/packages/backend` and run the
diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md
index e555c6b52d..97876c2ba1 100644
--- a/docs/getting-started/app-custom-theme.md
+++ b/docs/getting-started/app-custom-theme.md
@@ -1,6 +1,7 @@
---
id: app-custom-theme
title: Customize the look-and-feel of your App
+description: Documentation on Customizing look and feel of the App
---
Backstage ships with a default theme with a light and dark mode variant. The
diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md
index 515b52afa7..1d02f147c1 100644
--- a/docs/getting-started/configure-app-with-plugins.md
+++ b/docs/getting-started/configure-app-with-plugins.md
@@ -1,6 +1,7 @@
---
id: configure-app-with-plugins
title: Configuring App with plugins
+description: Documentation on How Configuring App with plugins
---
## Adding existing plugins to your app
diff --git a/docs/getting-started/create-an-app.md b/docs/getting-started/create-an-app.md
index cebf3f5de2..452e90add5 100644
--- a/docs/getting-started/create-an-app.md
+++ b/docs/getting-started/create-an-app.md
@@ -1,6 +1,7 @@
---
id: create-an-app
title: Create an App
+description: Documentation on Creating an App
---
To get set up quickly with your own Backstage project you can create a Backstage
diff --git a/docs/getting-started/deployment-k8s.md b/docs/getting-started/deployment-k8s.md
index 2a184779e2..ddcbd290c6 100644
--- a/docs/getting-started/deployment-k8s.md
+++ b/docs/getting-started/deployment-k8s.md
@@ -1,6 +1,7 @@
---
id: deployment-k8s
title: Kubernetes
+description: Documentation on Kubernetes and K8s Deployment
---
Coming soon!
diff --git a/docs/getting-started/deployment-other.md b/docs/getting-started/deployment-other.md
index d3fb9d932b..2cd9708e2a 100644
--- a/docs/getting-started/deployment-other.md
+++ b/docs/getting-started/deployment-other.md
@@ -1,6 +1,7 @@
---
id: deployment-other
title: Other
+description: Documentation on different ways of Deployment
---
## Deploying Locally
diff --git a/docs/getting-started/development-environment.md b/docs/getting-started/development-environment.md
index 9c75c8eabf..154a959f85 100644
--- a/docs/getting-started/development-environment.md
+++ b/docs/getting-started/development-environment.md
@@ -1,6 +1,8 @@
---
id: development-environment
title: Development Environment
+description: Documentation on how to get set up for doing development on
+the Backstage repository
---
This section describes how to get set up for doing development on the Backstage
diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md
index c1c6ea9231..a9ac770f90 100644
--- a/docs/getting-started/index.md
+++ b/docs/getting-started/index.md
@@ -1,84 +1,51 @@
---
id: index
-title: Running Backstage Locally
+title: Getting Started
+description: Documentation on How to get started with Backstage
---
-First make sure you are using NodeJS with an Active LTS Release, currently v12.
-This is made easy with a version manager such as nvm which allows for version
-switching.
+There are two different ways to get started with Backstage, either by creating a
+standalone app, or by cloning this repo. Which method you use depends on what
+you're planning to do.
+
+Creating a standalone instance makes it simpler to customize the application for
+your needs whilst staying up to date with the project. You will also depend on
+`@backstage` packages from NPM, making the project much smaller. This is the
+recommended approach if you want to kick the tyres of Backstage or setup your
+own instance.
+
+On the other hand, if you want to contribute plugins or to the project in
+general, it's easier to fork and clone this project. That will let you stay up
+to date with the latest changes, and gives you an easier path to make Pull
+Requests towards this repo.
+
+### Creating a Standalone App
+
+Backstage provides the `@backstage/create-app` package to scaffold standalone
+instances of Backstage. You will need to have
+[NodeJS](https://nodejs.org/en/download/) Active LTS Release installed
+(currently v12), and [yarn](https://classic.yarnpkg.com/en/docs/install). You
+will also need to have [Docker](https://docs.docker.com/engine/install/)
+installed to use some features like Software Templates and TechDocs.
+
+Using `npx` you can then run the following to create an app in a chosen
+subdirectory of your current working directory:
```bash
-# Checking your version
-node --version
-> v14.7.0
-
-# Adding a second node version
-nvm install 12
-> Downloading and installing node v12.18.3...
-> Now using node v12.18.3 (npm v6.14.6)
+npx @backstage/create-app
```
-To get up and running with a local Backstage to evaluate it, let's clone it off
-of GitHub and run an initial build.
+You will be taken through a wizard to create your app, and the output should
+look something like this. You can read more about this process
+[here](https://backstage.io/docs/getting-started/create-an-app).
-```bash
-# Start from your local development folder
-git clone git@github.com:spotify/backstage.git
-cd backstage
+### Contributing to Backstage
-# Fetch our dependencies and run an initial build
-yarn install
-yarn tsc
-yarn build
-```
+You can read more in our
+[CONTRIBUTING](https://github.com/spotify/backstage/blob/master/CONTRIBUTING.md)
+guide, which can help you get setup with a Backstage development environment.
-Phew! Now you have a local repository that's ready to run and to add any open
-source contributions into.
+### Next steps
-We are now going to launch two things: an example Backstage frontend app, and an
-example Backstage backend that the frontend talks to. You are going to need two
-terminal windows, both starting from the Backstage project root.
-
-In the first window, run
-
-```bash
-cd packages/backend
-yarn start
-```
-
-That starts up a backend instance on port 7000.
-
-In the other window, we will first populate the catalog with some nice mock data
-to look at, and then launch the frontend. These commands are run from the
-project root, not inside the backend directory.
-
-```bash
-yarn lerna run mock-data
-yarn start
-```
-
-That starts up the frontend on port 3000, and should automatically open a
-browser window showing it.
-
-Congratulations! That should be it. Let us know how it went
-[on discord](https://discord.gg/EBHEGzX), file issues for any
-[feature](https://github.com/spotify/backstage/issues/new?labels=help+wanted&template=feature_template.md)
-or
-[plugin suggestions](https://github.com/spotify/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME),
-or
-[bugs](https://github.com/spotify/backstage/issues/new?labels=bug&template=bug_template.md)
-you have, and feel free to
-[contribute](https://github.com/spotify/backstage/blob/master/CONTRIBUTING.md)!
-
-## Creating a Plugin
-
-The value of Backstage grows with every new plugin that gets added. Here is a
-collection of tutorials that will guide you through setting up and extending an
-instance of Backstage with your own plugins.
-
-- [Development Environment](development-environment.md)
-- [Create a Backstage Plugin](../plugins/create-a-plugin.md)
-- [Structure of a Plugin](../plugins/structure-of-a-plugin.md)
-- [Utility APIs](../api/utility-apis.md)
-
-[Back to Docs](../README.md)
+Take a look at the [Running Backstage Locally](./running-backstage-locally.md)
+guide to learn how to set up Backstage, and how to develop on the platform.
diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md
index a4f91b6e95..3b11d2b8e1 100644
--- a/docs/getting-started/installation.md
+++ b/docs/getting-started/installation.md
@@ -1,6 +1,7 @@
---
id: installation
title: Installation
+description: Documentation on Installation
---
Coming soon!
diff --git a/docs/getting-started/running-backstage-locally.md b/docs/getting-started/running-backstage-locally.md
new file mode 100644
index 0000000000..a0cf10998e
--- /dev/null
+++ b/docs/getting-started/running-backstage-locally.md
@@ -0,0 +1,110 @@
+---
+id: running-backstage-locally
+title: Running Backstage Locally
+description: Documentation on How to run Backstage Locally
+---
+
+## Prerequisites
+
+- Node.js
+
+First make sure you are using NodeJS with an Active LTS Release, currently v12.
+This is made easy with a version manager such as
+[nvm](https://github.com/nvm-sh/nvm) which allows for version switching.
+
+```bash
+# Installing a new version
+nvm install 12
+> Downloading and installing node v12.18.3...
+> Now using node v12.18.3 (npm v6.14.6)
+
+# Checking your version
+node --version
+> v12.18.3
+```
+
+- yarn
+
+Please refer to the
+[installation instructions for yarn](https://classic.yarnpkg.com/en/docs/install/).
+
+- Docker
+
+We use Docker for few of our core features. So, you will need Docker installed
+locally to use features like Software Templates and TechDocs. Please refer to
+the
+[installation instructions for Docker](https://docs.docker.com/engine/install/).
+
+## Clone and Build
+
+To get up and running with a local Backstage to evaluate it, let's clone it off
+of GitHub and run an initial build.
+
+```bash
+# Start from your local development folder
+git clone git@github.com:spotify/backstage.git
+cd backstage
+
+# Fetch our dependencies and run an initial build
+yarn install
+yarn tsc
+yarn build
+```
+
+Phew! Now you have a local repository that's ready to run and to add any open
+source contributions into.
+
+We are now going to launch two things: an example Backstage frontend app, and an
+example Backstage backend that the frontend talks to. You are going to need two
+terminal windows, both starting from the Backstage project root.
+
+In the first window, run
+
+```bash
+cd packages/backend
+yarn start
+```
+
+That starts up a backend instance on port 7000.
+
+In the other window, we will then launch the frontend. This command is run from
+the project root, not inside the backend directory.
+
+```bash
+yarn start
+```
+
+That starts up the frontend on port 3000, and should automatically open a
+browser window showing it.
+
+## Authentication
+
+When Backstage starts, you can choose to enter as a Guest user and start
+exploring.
+
+But you can also set up any of the available authentication methods. The easiest
+option will be GitHub. To setup GitHub authentication in Backstage, see
+[these instructions](https://github.com/spotify/backstage/tree/master/plugins/auth-backend#github).
+
+---
+
+Congratulations! That should be it. Let us know how it went
+[on discord](https://discord.gg/EBHEGzX), file issues for any
+[feature](https://github.com/spotify/backstage/issues/new?labels=help+wanted&template=feature_template.md)
+or
+[plugin suggestions](https://github.com/spotify/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME),
+or
+[bugs](https://github.com/spotify/backstage/issues/new?labels=bug&template=bug_template.md)
+you have, and feel free to
+[contribute](https://github.com/spotify/backstage/blob/master/CONTRIBUTING.md)!
+
+## Creating a Plugin
+
+The value of Backstage grows with every new plugin that gets added. Here is a
+collection of tutorials that will guide you through setting up and extending an
+instance of Backstage with your own plugins.
+
+- [Development Environment](development-environment.md)
+- [Create a Backstage Plugin](../plugins/create-a-plugin.md)
+- [Structure of a Plugin](../plugins/structure-of-a-plugin.md)
+- [Utility APIs](../api/utility-apis.md)
diff --git a/docs/overview/adopting.md b/docs/overview/adopting.md
index 893eac6398..422adfb288 100644
--- a/docs/overview/adopting.md
+++ b/docs/overview/adopting.md
@@ -1,6 +1,8 @@
---
id: adopting
title: Strategies for adopting
+description: Documentation on some general best practices that have been key
+to Backstage's success inside Spotify
---
This document outlines some general best practices that have been key to
diff --git a/docs/overview/architecture-overview.md b/docs/overview/architecture-overview.md
index 55a1aeec04..f2c6dad61f 100644
--- a/docs/overview/architecture-overview.md
+++ b/docs/overview/architecture-overview.md
@@ -1,6 +1,7 @@
---
id: architecture-overview
title: Architecture overview
+description: Documentation on Architecture overview
---
## Overview
diff --git a/docs/overview/architecture-terminology.md b/docs/overview/architecture-terminology.md
index aee581f927..092ca7fb01 100644
--- a/docs/overview/architecture-terminology.md
+++ b/docs/overview/architecture-terminology.md
@@ -1,6 +1,7 @@
---
id: architecture-terminology
title: Architecture terminology
+description: Documentation on Architecture terminology
---
Backstage is constructed out of three parts. We separate Backstage in this way
diff --git a/docs/overview/background.md b/docs/overview/background.md
index ef226653fb..486260bac3 100644
--- a/docs/overview/background.md
+++ b/docs/overview/background.md
@@ -1,6 +1,7 @@
---
id: background
title: The Spotify Story
+description: Documentation on Background and Story behind making of Backstage
---
Backstage was born out of necessity at Spotify. We found that as we grew, our
diff --git a/docs/overview/logos.md b/docs/overview/logos.md
index 39071ed793..2a684420bc 100644
--- a/docs/overview/logos.md
+++ b/docs/overview/logos.md
@@ -2,6 +2,7 @@
id: logos
title: Logos
sidebar_label: Logo assets
+description: Guidelines for how to use the Backstage logos and icons
---
Guidelines for how to use the Backstage logo and icon can be found
diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md
index 5a06f4544f..d6c1d2d641 100644
--- a/docs/overview/roadmap.md
+++ b/docs/overview/roadmap.md
@@ -1,6 +1,7 @@
---
id: roadmap
title: Project roadmap
+description: Roadmap of Backstage Project
---
## Current status
diff --git a/docs/overview/support.md b/docs/overview/support.md
index 9e4e9eca15..6e00c17fab 100644
--- a/docs/overview/support.md
+++ b/docs/overview/support.md
@@ -1,6 +1,7 @@
---
id: support
title: Support and community
+description: Support and Community Details and Links
---
- [Discord chatroom](https://discord.gg/MUpMjP2) - Get support or discuss the
diff --git a/docs/overview/vision.md b/docs/overview/vision.md
index 2ab7cdc6f4..df75a4f0a6 100644
--- a/docs/overview/vision.md
+++ b/docs/overview/vision.md
@@ -1,6 +1,8 @@
---
id: vision
title: Vision
+description: Goal is to provide engineers with the best developer experience in
+the world
---
Our goal is to provide engineers with the best developer experience in the
diff --git a/docs/overview/what-is-backstage.md b/docs/overview/what-is-backstage.md
index 96e89618a8..0d16943c9b 100644
--- a/docs/overview/what-is-backstage.md
+++ b/docs/overview/what-is-backstage.md
@@ -1,6 +1,8 @@
---
id: what-is-backstage
title: What is Backstage?
+description: Backsatge is an open platform for building developer portals.
+Powered by a centralized service catalog, Backstage restores order to your microservices and infrastructure
---

diff --git a/docs/plugins/add-to-marketplace.md b/docs/plugins/add-to-marketplace.md
index d85807a226..59cca07bff 100644
--- a/docs/plugins/add-to-marketplace.md
+++ b/docs/plugins/add-to-marketplace.md
@@ -1,6 +1,7 @@
---
id: add-to-marketplace
title: Add to Marketplace
+description: Documentation on Adding Plugin to Marketplace
---
## Adding a Plugin to the Marketplace
diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md
index 986af66c20..516ba4b111 100644
--- a/docs/plugins/backend-plugin.md
+++ b/docs/plugins/backend-plugin.md
@@ -1,6 +1,7 @@
---
id: backend-plugin
title: Backend plugin
+description: Documentation on Backend plugin
---
## TODO
diff --git a/docs/plugins/call-existing-api.md b/docs/plugins/call-existing-api.md
index 9658176224..14ea4c1f5a 100644
--- a/docs/plugins/call-existing-api.md
+++ b/docs/plugins/call-existing-api.md
@@ -1,6 +1,8 @@
---
id: call-existing-api
title: Call Existing API
+description: Describes the various options that Backstage frontend plugins have,
+in communicating with service APIs that already exist
---
This article describes the various options that Backstage frontend plugins have,
diff --git a/docs/plugins/create-a-plugin.md b/docs/plugins/create-a-plugin.md
index 4bcc4da387..bcb86024fd 100644
--- a/docs/plugins/create-a-plugin.md
+++ b/docs/plugins/create-a-plugin.md
@@ -1,6 +1,7 @@
---
id: create-a-plugin
title: Create a Backstage Plugin
+description: Documentation on How to Create a Backstage Plugin
---
A Backstage Plugin adds functionality to Backstage.
diff --git a/docs/plugins/existing-plugins.md b/docs/plugins/existing-plugins.md
index 8598ae02c4..c1fbda379a 100644
--- a/docs/plugins/existing-plugins.md
+++ b/docs/plugins/existing-plugins.md
@@ -1,6 +1,7 @@
---
id: existing-plugins
title: Existing plugins
+description: Lists of existing open source plugins
---
## Open source plugins
diff --git a/docs/plugins/index.md b/docs/plugins/index.md
index 5aaf8acadf..8a61aeee80 100644
--- a/docs/plugins/index.md
+++ b/docs/plugins/index.md
@@ -1,6 +1,7 @@
---
id: index
title: Intro to plugins
+description: Documentation on Introduction to Plugins
---
Backstage is a single-page application composed of a set of plugins.
diff --git a/docs/plugins/integrating-plugin-into-service-catalog.md b/docs/plugins/integrating-plugin-into-service-catalog.md
index 4408c27ccc..51d2331140 100644
--- a/docs/plugins/integrating-plugin-into-service-catalog.md
+++ b/docs/plugins/integrating-plugin-into-service-catalog.md
@@ -1,6 +1,7 @@
---
id: integrating-plugin-into-service-catalog
title: Integrate into the Service Catalog
+description: Documentation on How to integrate plugin into service catalog
---
> This is an advanced use case and currently is an experimental feature. Expect
diff --git a/docs/plugins/plugin-development.md b/docs/plugins/plugin-development.md
index faf3c6ddbd..c5d9763f7d 100644
--- a/docs/plugins/plugin-development.md
+++ b/docs/plugins/plugin-development.md
@@ -1,6 +1,7 @@
---
id: plugin-development
title: Plugin Development
+description: Documentation on Plugin Development
---
Backstage plugins provide features to a Backstage App.
diff --git a/docs/plugins/proxying.md b/docs/plugins/proxying.md
index 658df3dda1..2f809ffd10 100644
--- a/docs/plugins/proxying.md
+++ b/docs/plugins/proxying.md
@@ -1,6 +1,7 @@
---
id: proxying
title: Proxying
+description: Documentation on Proxying
---
## Overview
@@ -52,7 +53,10 @@ configuration will lead to the proxy acting on backend requests to
The value inside each route is either a simple URL string, or an object on the
format accepted by
-[http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware).
+[http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware). It
+is also possible to limit the forwarded HTTP methods with the configuration
+`allowedMethods`, for example `allowedMethods: ['GET']` to enforce read-only
+access.
If the value is a string, it is assumed to correspond to:
diff --git a/docs/plugins/publish-private.md b/docs/plugins/publish-private.md
index 6361f70854..d04449d465 100644
--- a/docs/plugins/publish-private.md
+++ b/docs/plugins/publish-private.md
@@ -1,6 +1,7 @@
---
id: publish-private
title: Publish private
+description: Documentation on How to Publish private
---
## TODO
diff --git a/docs/plugins/publishing.md b/docs/plugins/publishing.md
index 6fee4f9b1d..0ed85e8cdc 100644
--- a/docs/plugins/publishing.md
+++ b/docs/plugins/publishing.md
@@ -1,6 +1,7 @@
---
id: publishing
title: Publishing
+description: Documentation on Publishing NPM packages
---
## NPM
diff --git a/docs/plugins/structure-of-a-plugin.md b/docs/plugins/structure-of-a-plugin.md
index 20e72c539d..f061d2f4a8 100644
--- a/docs/plugins/structure-of-a-plugin.md
+++ b/docs/plugins/structure-of-a-plugin.md
@@ -1,6 +1,7 @@
---
id: structure-of-a-plugin
title: Structure of a Plugin
+description: Details about structure of a plugin
---
Nice, you have a new plugin! We'll soon see how we can develop it into doing
diff --git a/docs/plugins/testing.md b/docs/plugins/testing.md
index 110bc97515..696f8b1368 100644
--- a/docs/plugins/testing.md
+++ b/docs/plugins/testing.md
@@ -1,6 +1,7 @@
---
id: testing
title: Testing with Jest
+description: Documentation on How to do unit testing with Jest
---
Backstage uses [Jest](https://facebook.github.io/jest/) for all our unit testing
diff --git a/docs/reference/createPlugin-feature-flags.md b/docs/reference/createPlugin-feature-flags.md
index bc38656939..ebb8a6b503 100644
--- a/docs/reference/createPlugin-feature-flags.md
+++ b/docs/reference/createPlugin-feature-flags.md
@@ -1,6 +1,7 @@
---
id: createPlugin-feature-flags
title: createPlugin - feature flags
+description: Documentation on createPlugin - feature flags
---
The `featureFlags` object passed to the `register` function makes it possible
diff --git a/docs/reference/createPlugin-router.md b/docs/reference/createPlugin-router.md
index 361b485df9..89ee44e558 100644
--- a/docs/reference/createPlugin-router.md
+++ b/docs/reference/createPlugin-router.md
@@ -1,6 +1,7 @@
---
id: createPlugin-router
title: createPlugin - router
+description: Documentation on createPlugin - router
---
The router that is passed to the `register` function makes it possible for
diff --git a/docs/reference/createPlugin.md b/docs/reference/createPlugin.md
index 0cabe63b39..45e3303124 100644
--- a/docs/reference/createPlugin.md
+++ b/docs/reference/createPlugin.md
@@ -1,6 +1,7 @@
---
id: createPlugin
title: createPlugin
+description: Documentation on createPlugin
---
Taking a plugin config as argument and returns a new plugin.
diff --git a/docs/reference/utility-apis/AlertApi.md b/docs/reference/utility-apis/AlertApi.md
index d000a6d6a2..d096276c3b 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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/AlertApi.ts#L29).
+[packages/core-api/src/apis/definitions/AlertApi.ts:29](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/AlertApi.ts#L19).
+[packages/core-api/src/apis/definitions/AlertApi.ts:19](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L53).
+[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L24).
+[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L33).
+[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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 e7d5296ceb..d37282adea 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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/AppThemeApi.ts#L50).
+[packages/core-api/src/apis/definitions/AppThemeApi.ts:50](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/AppThemeApi.ts#L24).
+[packages/core-api/src/apis/definitions/AppThemeApi.ts:24](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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:70](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/theme/src/types.ts#L70).
+[packages/theme/src/types.ts:70](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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:73](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/theme/src/types.ts#L73).
+[packages/theme/src/types.ts:73](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L53).
+[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L24).
+[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/theme/src/types.ts#L23).
+[packages/theme/src/types.ts:23](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L33).
+[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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 87318ecff5..529b73c576 100644
--- a/docs/reference/utility-apis/BackstageIdentityApi.md
+++ b/docs/reference/utility-apis/BackstageIdentityApi.md
@@ -1,7 +1,7 @@
# BackstageIdentityApi
The BackstageIdentityApi type is defined at
-[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).
+[packages/core-api/src/apis/definitions/auth.ts:134](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L134).
The following Utility APIs implement this type:
@@ -15,6 +15,8 @@ The following Utility APIs implement this type:
- [microsoftAuthApiRef](./README.md#microsoftauth)
+- [oauth2ApiRef](./README.md#oauth2)
+
- [oktaAuthApiRef](./README.md#oktaauth)
## Members
@@ -66,7 +68,7 @@ export type AuthRequestOptions = {
Defined at
-[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).
+[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L40).
Referenced by: [getBackstageIdentity](#getbackstageidentity).
@@ -87,6 +89,6 @@ export type BackstageIdentity = {
Defined at
-[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).
+[packages/core-api/src/apis/definitions/auth.ts:147](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L147).
Referenced by: [getBackstageIdentity](#getbackstageidentity).
diff --git a/docs/reference/utility-apis/Config.md b/docs/reference/utility-apis/Config.md
index 9374fb9962..b54e122c03 100644
--- a/docs/reference/utility-apis/Config.md
+++ b/docs/reference/utility-apis/Config.md
@@ -1,7 +1,7 @@
# Config
The Config type is defined at
-[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/config/src/types.ts#L32).
+[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/config/src/types.ts#L32).
The following Utility API implements this type:
[configApiRef](./README.md#config)
@@ -140,7 +140,7 @@ export type Config = {
Defined at
-[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/config/src/types.ts#L32).
+[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/config/src/types.ts#L32).
Referenced by: [getConfig](#getconfig), [getOptionalConfig](#getoptionalconfig),
[getConfigArray](#getconfigarray),
@@ -153,7 +153,7 @@ export type JsonArray = JsonValue []
Defined at
-[packages/config/src/types.ts:18](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/config/src/types.ts#L18).
+[packages/config/src/types.ts:18](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/config/src/types.ts#L18).
Referenced by: [JsonValue](#jsonvalue).
@@ -164,7 +164,7 @@ export type JsonObject = { [key in string]?: JsonValue
Defined at
-[packages/config/src/types.ts:17](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/config/src/types.ts#L17).
+[packages/config/src/types.ts:17](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/config/src/types.ts#L17).
Referenced by: [JsonValue](#jsonvalue).
@@ -181,7 +181,7 @@ export type JsonValue =
Defined at
-[packages/config/src/types.ts:19](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/config/src/types.ts#L19).
+[packages/config/src/types.ts:19](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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
index 39902789cd..24371c1729 100644
--- a/docs/reference/utility-apis/DiscoveryApi.md
+++ b/docs/reference/utility-apis/DiscoveryApi.md
@@ -1,7 +1,7 @@
# 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).
+[packages/core-api/src/apis/definitions/DiscoveryApi.ts:30](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/DiscoveryApi.ts#L30).
The following Utility API implements this type:
[discoveryApiRef](./README.md#discovery)
diff --git a/docs/reference/utility-apis/ErrorApi.md b/docs/reference/utility-apis/ErrorApi.md
index c05f060ec3..4de58b34a1 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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/ErrorApi.ts#L53).
+[packages/core-api/src/apis/definitions/ErrorApi.ts:53](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/ErrorApi.ts#L24).
+[packages/core-api/src/apis/definitions/ErrorApi.ts:24](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/ErrorApi.ts#L33).
+[packages/core-api/src/apis/definitions/ErrorApi.ts:33](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L53).
+[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L24).
+[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L33).
+[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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 5efdb176a4..8fbcb794fb 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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L41).
+[packages/core-api/src/apis/definitions/FeatureFlagsApi.ts:41](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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 4e37ad5300..5ee5c582b6 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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/IdentityApi.ts#L22).
+[packages/core-api/src/apis/definitions/IdentityApi.ts:22](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/IdentityApi.ts#L22).
The following Utility API implements this type:
[identityApiRef](./README.md#identity)
@@ -40,12 +40,12 @@ identity, such as a demo user or mocked user for e2e tests.
getIdToken(): Promise<string | undefined>
-### logout()
+### signOut()
-Log out the current user
+Sign out the current user
-logout(): Promise<void>
+signOut(): Promise<void>
## Supporting types
@@ -76,6 +76,6 @@ export type ProfileInfo = {
Defined at
-[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).
+[packages/core-api/src/apis/definitions/auth.ts:162](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L162).
Referenced by: [getProfile](#getprofile).
diff --git a/docs/reference/utility-apis/OAuthApi.md b/docs/reference/utility-apis/OAuthApi.md
index e73d5f645f..a489db76c5 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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L67).
+[packages/core-api/src/apis/definitions/auth.ts:67](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L67).
The following Utility APIs implement this type:
@@ -50,14 +50,6 @@ getAccessToken(
): Promise<string>
-### logout()
-
-Log out the user's session. This will reload the page.
-
-
-logout(): Promise<void>
-
-
## Supporting types
These types are part of the API declaration, but may not be unique to this API.
@@ -90,7 +82,7 @@ export type AuthRequestOptions = {
Defined at
-[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).
+[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L40).
Referenced by: [getAccessToken](#getaccesstoken).
@@ -116,6 +108,6 @@ export type OAuthScope = string | string[]
Defined at
-[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).
+[packages/core-api/src/apis/definitions/auth.ts:38](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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 c6b9e09189..ac11764682 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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L99).
+[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:99](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L27).
+[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:27](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L66).
+[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:66](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L43).
+[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:43](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L53).
+[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L24).
+[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L77).
+[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:77](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L33).
+[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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 41a3247af6..efd79593d6 100644
--- a/docs/reference/utility-apis/OpenIdConnectApi.md
+++ b/docs/reference/utility-apis/OpenIdConnectApi.md
@@ -1,7 +1,7 @@
# OpenIdConnectApi
The OpenIdConnectApi type is defined at
-[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).
+[packages/core-api/src/apis/definitions/auth.ts:99](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L99).
The following Utility APIs implement this type:
@@ -34,14 +34,6 @@ user rejects the login request.
getIdToken(options?: AuthRequestOptions ): Promise<string>
-### logout()
-
-Log out the user's session. This will reload the page.
-
-
-logout(): Promise<void>
-
-
## Supporting types
These types are part of the API declaration, but may not be unique to this API.
@@ -74,6 +66,6 @@ export type AuthRequestOptions = {
Defined at
-[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).
+[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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 09c0f88f83..402b5ba504 100644
--- a/docs/reference/utility-apis/ProfileInfoApi.md
+++ b/docs/reference/utility-apis/ProfileInfoApi.md
@@ -1,7 +1,7 @@
# ProfileInfoApi
The ProfileInfoApi type is defined at
-[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).
+[packages/core-api/src/apis/definitions/auth.ts:117](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L117).
The following Utility APIs implement this type:
@@ -65,7 +65,7 @@ export type AuthRequestOptions = {
Defined at
-[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).
+[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L40).
Referenced by: [getProfile](#getprofile).
@@ -93,6 +93,6 @@ export type ProfileInfo = {
Defined at
-[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).
+[packages/core-api/src/apis/definitions/auth.ts:162](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L162).
Referenced by: [getProfile](#getprofile).
diff --git a/docs/reference/utility-apis/README.md b/docs/reference/utility-apis/README.md
index cfdc3b6ef8..71931a5d5a 100644
--- a/docs/reference/utility-apis/README.md
+++ b/docs/reference/utility-apis/README.md
@@ -12,7 +12,7 @@ Used to report alerts and forward them to the app
Implemented type: [AlertApi](./AlertApi.md)
ApiRef:
-[alertApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/AlertApi.ts#L41)
+[alertApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/AlertApi.ts#L41)
### appTheme
@@ -21,7 +21,7 @@ API Used to configure the app theme, and enumerate options
Implemented type: [AppThemeApi](./AppThemeApi.md)
ApiRef:
-[appThemeApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/AppThemeApi.ts#L74)
+[appThemeApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/AppThemeApi.ts#L74)
### auth0Auth
@@ -29,11 +29,10 @@ Provides authentication towards Auth0 APIs
Implemented types: [OpenIdConnectApi](./OpenIdConnectApi.md),
[ProfileInfoApi](./ProfileInfoApi.md),
-[BackstageIdentityApi](./BackstageIdentityApi.md),
-[SessionStateApi](./SessionStateApi.md)
+[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
ApiRef:
-[auth0AuthApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L273)
+[auth0AuthApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L275)
### config
@@ -42,7 +41,7 @@ Used to access runtime configuration
Implemented type: [Config](./Config.md)
ApiRef:
-[configApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/ConfigApi.ts#L22)
+[configApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/ConfigApi.ts#L22)
### discovery
@@ -51,7 +50,7 @@ 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)
+[discoveryApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/DiscoveryApi.ts#L44)
### error
@@ -60,7 +59,7 @@ Used to report errors and forward them to the app
Implemented type: [ErrorApi](./ErrorApi.md)
ApiRef:
-[errorApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/ErrorApi.ts#L65)
+[errorApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/ErrorApi.ts#L65)
### featureFlags
@@ -69,7 +68,7 @@ Used to toggle functionality in features across Backstage
Implemented type: [FeatureFlagsApi](./FeatureFlagsApi.md)
ApiRef:
-[featureFlagsApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L58)
+[featureFlagsApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L58)
### githubAuth
@@ -77,11 +76,10 @@ Provides authentication towards GitHub APIs
Implemented types: [OAuthApi](./OAuthApi.md),
[ProfileInfoApi](./ProfileInfoApi.md),
-[BackstageIdentityApi](./BackstageIdentityApi.md),
-[SessionStateApi](./SessionStateApi.md)
+[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
ApiRef:
-[githubAuthApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L230)
+[githubAuthApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L232)
### gitlabAuth
@@ -89,11 +87,10 @@ Provides authentication towards GitLab APIs
Implemented types: [OAuthApi](./OAuthApi.md),
[ProfileInfoApi](./ProfileInfoApi.md),
-[BackstageIdentityApi](./BackstageIdentityApi.md),
-[SessionStateApi](./SessionStateApi.md)
+[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
ApiRef:
-[gitlabAuthApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L260)
+[gitlabAuthApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L262)
### googleAuth
@@ -102,11 +99,10 @@ Provides authentication towards Google APIs and identities
Implemented types: [OAuthApi](./OAuthApi.md),
[OpenIdConnectApi](./OpenIdConnectApi.md),
[ProfileInfoApi](./ProfileInfoApi.md),
-[BackstageIdentityApi](./BackstageIdentityApi.md),
-[SessionStateApi](./SessionStateApi.md)
+[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
ApiRef:
-[googleAuthApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L213)
+[googleAuthApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L215)
### identity
@@ -115,7 +111,7 @@ Provides access to the identity of the signed in user
Implemented type: [IdentityApi](./IdentityApi.md)
ApiRef:
-[identityApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/IdentityApi.ts#L54)
+[identityApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/IdentityApi.ts#L54)
### microsoftAuth
@@ -124,11 +120,10 @@ Provides authentication towards Microsoft APIs and identities
Implemented types: [OAuthApi](./OAuthApi.md),
[OpenIdConnectApi](./OpenIdConnectApi.md),
[ProfileInfoApi](./ProfileInfoApi.md),
-[BackstageIdentityApi](./BackstageIdentityApi.md),
-[SessionStateApi](./SessionStateApi.md)
+[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
ApiRef:
-[microsoftAuthApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L287)
+[microsoftAuthApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L289)
### oauth2
@@ -136,10 +131,11 @@ Example of how to use oauth2 custom provider
Implemented types: [OAuthApi](./OAuthApi.md),
[OpenIdConnectApi](./OpenIdConnectApi.md),
-[ProfileInfoApi](./ProfileInfoApi.md), [SessionStateApi](./SessionStateApi.md)
+[ProfileInfoApi](./ProfileInfoApi.md),
+[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
ApiRef:
-[oauth2ApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L301)
+[oauth2ApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L303)
### oauthRequest
@@ -148,7 +144,7 @@ An API for implementing unified OAuth flows in Backstage
Implemented type: [OAuthRequestApi](./OAuthRequestApi.md)
ApiRef:
-[oauthRequestApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L130)
+[oauthRequestApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L130)
### oktaAuth
@@ -157,11 +153,10 @@ Provides authentication towards Okta APIs
Implemented types: [OAuthApi](./OAuthApi.md),
[OpenIdConnectApi](./OpenIdConnectApi.md),
[ProfileInfoApi](./ProfileInfoApi.md),
-[BackstageIdentityApi](./BackstageIdentityApi.md),
-[SessionStateApi](./SessionStateApi.md)
+[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
ApiRef:
-[oktaAuthApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L243)
+[oktaAuthApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L245)
### storage
@@ -170,4 +165,4 @@ 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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/StorageApi.ts#L68)
+[storageApiRef](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/StorageApi.ts#L68)
diff --git a/docs/reference/utility-apis/SessionApi.md b/docs/reference/utility-apis/SessionApi.md
new file mode 100644
index 0000000000..5e40b3317b
--- /dev/null
+++ b/docs/reference/utility-apis/SessionApi.md
@@ -0,0 +1,138 @@
+# SessionApi
+
+The SessionApi type is defined at
+[packages/core-api/src/apis/definitions/auth.ts:190](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L190).
+
+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)
+
+## Members
+
+### signIn()
+
+Sign in with a minimum set of permissions.
+
+
+signIn(): Promise<void>
+
+
+### signOut()
+
+Sign out from the current session. This will reload the page.
+
+
+signOut(): Promise<void>
+
+
+### sessionState\$()
+
+Observe the current state of the auth session. Emits the current state on
+subscription.
+
+
+sessionState$(): Observable <SessionState >
+
+
+## Supporting types
+
+These types are part of the API declaration, but may not be unique to this API.
+
+### Observable
+
+Observable sequence of values and errors, see TC39.
+
+https://github.com/tc39/proposal-observable
+
+This is used as a common return type for observable values and can be created
+using many different observable implementations, such as zen-observable or
+RxJS 5.
+
+
+export type Observable<T> = {
+ /**
+ * Subscribes to this observable to start receiving new values.
+ */
+ subscribe(observer: Observer <T>): Subscription ;
+ subscribe(
+ onNext: (value: T) => void,
+ onError?: (error: Error) => void,
+ onComplete?: () => void,
+ ): Subscription ;
+}
+
+
+Defined at
+[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/types.ts#L53).
+
+Referenced by: [sessionState\$](#sessionstate).
+
+### Observer
+
+This file contains non-react related core types used throught Backstage.
+
+Observer interface for consuming an Observer, see TC39.
+
+
+export type Observer<T> = {
+ next?(value: T): void;
+ error?(error: Error): void;
+ complete?(): void;
+}
+
+
+Defined at
+[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/types.ts#L24).
+
+Referenced by: [Observable](#observable).
+
+### SessionState
+
+Session state values passed to subscribers of the SessionApi.
+
+
+export enum SessionState {
+ SignedIn = 'SignedIn',
+ SignedOut = 'SignedOut',
+}
+
+
+Defined at
+[packages/core-api/src/apis/definitions/auth.ts:182](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/apis/definitions/auth.ts#L182).
+
+Referenced by: [sessionState\$](#sessionstate).
+
+### Subscription
+
+Subscription returned when subscribing to an Observable, see TC39.
+
+
+export type Subscription = {
+ /**
+ * Cancels the subscription
+ */
+ unsubscribe(): void;
+
+ /**
+ * Value indicating whether the subscription is closed.
+ */
+ readonly closed: Boolean;
+}
+
+
+Defined at
+[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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 bee52935da..3fd75a42d6 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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/StorageApi.ts#L31).
+[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L53).
+[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L24).
+[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/StorageApi.ts#L31).
+[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/StorageApi.ts#L21).
+[packages/core-api/src/apis/definitions/StorageApi.ts:21](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/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/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L33).
+[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/0406ace29aba7332a98ff9ef9feedd65adc75223/packages/core-api/src/types.ts#L33).
Referenced by: [Observable](#observable).
diff --git a/docs/tutorials/journey.md b/docs/tutorials/journey.md
index 3c399c53ba..24765e8531 100644
--- a/docs/tutorials/journey.md
+++ b/docs/tutorials/journey.md
@@ -1,6 +1,7 @@
---
id: journey
title: Future developer journey
+description: This document describes a possible journey of a future Backstage
---
> This document describes a possible journey of a **_future_** Backstage plugin
diff --git a/docs/tutorials/quickstart-app-auth.md b/docs/tutorials/quickstart-app-auth.md
new file mode 100644
index 0000000000..82f9cab537
--- /dev/null
+++ b/docs/tutorials/quickstart-app-auth.md
@@ -0,0 +1,200 @@
+---
+id: quickstart-app-auth
+title: Monorepo App Setup With Authentication
+---
+
+###### September 15th 2020 - @backstage/create-app - v0.1.1-alpha.21
+
+
+
+> This document takes you through setting up a backstage app that runs in your
+> own environment. It starts with a skeleton install and verifying of the
+> monorepo's functionality. Next, GitHub authentication is added and tested.
+>
+> This document assumes you have NodeJS 12 active along with Yarn. Please note,
+> that at the time of this writing, the current version is 0.1.1-alpha.21. This
+> guide can still be used with future versions, just, verify as you go. If you
+> run into issues, you can compare your setup with mine here >
+> [simple-backstage-app](https://github.com/johnson-jesse/simple-backstage-app).
+
+# The Skeleton Application
+
+From the terminal:
+
+1. Create a (monorepo) application: `npx @backstage/create-app`
+1. Enter an `id` for your new app like `mybiz-backstage` I went with
+ `simple-backstage-app`
+1. Choose `SQLite` as your database. This is the quickest way to get started as
+ PostgreSQL requires additional setup not covered here.
+1. Start your backend: `yarn --cwd packages/backend start`
+
+```zsh
+# You should see positive verbiage in your terminal output
+2020-09-11T22:20:26.712Z backstage info Listening on :7000
+```
+
+5. Finally, start the frontend. Open a new terminal window and from the root of
+ your project, run: `yarn start`
+
+```zsh
+# You should see positive verbiage in your terminal output
+ℹ 「wds」: Project is running at http://localhost:3000/
+```
+
+Once the app compiles, a browser window should have popped with your stand-alone
+application loaded at `localhost:3000`. This could take a couple minutes.
+
+```zsh
+# You should see positive verbiage in your terminal output
+ℹℹ 「wdm」: Compiled successfully.
+```
+
+Since there is no auth currently configured, you are automatically entered as a
+guest. Let's fix that now and add auth.
+
+# The Auth Configuration
+
+1. Open `app-config.yaml` and change it as follows
+
+_from:_
+
+```yaml
+auth:
+ providers: {}
+```
+
+_to:_
+
+```yaml
+auth:
+ providers:
+ github:
+ development:
+ clientId:
+ $secret:
+ env: AUTH_GITHUB_CLIENT_ID
+ clientSecret:
+ $secret:
+ env: AUTH_GITHUB_CLIENT_SECRET
+ ## uncomment the following three lines if using enterprise
+ # enterpriseInstanceUrl:
+ # $secret:
+ # env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL
+```
+
+2. Set environment variables in whatever fashion is easiest for you. I chose to
+ add mine to my `.zshrc` profile.
+
+```zsh
+# For macOS Catalina & Z Shell
+# ------ simple-backstage-app GitHub
+export AUTH_GITHUB_CLIENT_ID=xxx
+export AUTH_GITHUB_CLIENT_SECRET=xxx
+# export AUTH_GITHUB_ENTERPRISE_INSTANCE_URL=https://github.{MY_BIZ}.com
+```
+
+3. And of course I need to source that file.
+
+```zsh
+# Loading the new variables
+% source ~/.zshrc
+
+# Any other currently opened terminals need to be restarted to pick up the new values
+# verify your setup by running env
+% env
+# should output something like
+> ...
+> AUTH_GITHUB_CLIENT_ID=xxx
+> AUTH_GITHUB_CLIENT_SECRET=xxx
+> ...
+```
+
+4. The values to replace `xxx` above come from your oauth app setup.
+
+```
+> Log into http://github.com
+> Navigate to (Settings > Developer Settings > OAuth Apps > New OAuth App)[https://github.com/settings/applications/new]
+> Set Homepage URL = http://localhost:3000
+> Set Callback URL = http://localhost:7000/auth/github
+> Click [Register application]
+> On the next page, copy and paste your new Client ID and Client Secret to the environment variables above, `AUTH_GITHUB_CLIENT_ID` & `AUTH_GITHUB_CLIENT_SECRET`
+> Don't forget to `source` that profile file again if necessary.
+```
+
+5. Open and change _root > packages > app > src >_`App.tsx` as follows
+
+```tsx
+// Add the following imports to the existing list from core
+import { githubAuthApiRef, SignInPage } from '@backstage/core';
+```
+
+6. In the same file, change the createApp function as follows
+
+```tsx
+const app = createApp({
+ apis,
+ plugins: Object.values(plugins),
+ components: {
+ SignInPage: props => {
+ return (
+
+ );
+ },
+ },
+});
+```
+
+6. Open and change _root > packages > app > src >_ `apis.ts` as follows
+
+```ts
+// Add the following imports to the existing list from core
+import { githubAuthApiRef, GithubAuth } from '@backstage/core';
+```
+
+7. In the same file, change the builder block for oauthRequestApiRef as follows
+
+_from:_
+
+```ts
+builder.add(oauthRequestApiRef, new OAuthRequestManager());
+```
+
+_to:_
+
+```ts
+const oauthRequestApi = builder.add(
+ oauthRequestApiRef,
+ new OAuthRequestManager(),
+);
+
+builder.add(
+ githubAuthApiRef,
+ GithubAuth.create({
+ discoveryApi,
+ oauthRequestApi,
+ }),
+);
+```
+
+8. Start the backend and frontend as before
+
+When the browser loads, you should be presented with a login page for GitHub.
+Login as usual with your GitHub account. If this is your first time, you will be
+asked to authorize and then are redirected to the catalog page if all is well.
+
+# Where to go from here
+
+> You're probably eager to write your first custom plugin. Follow this next
+> tutorial for an in-depth look at a custom GitHub repository browser plugin.
+> [Adding Custom Plugin to Existing Monorepo App](quickstart-app-plugin.md).
diff --git a/docs/tutorials/quickstart-app-plugin.md b/docs/tutorials/quickstart-app-plugin.md
new file mode 100644
index 0000000000..51a7f8ded6
--- /dev/null
+++ b/docs/tutorials/quickstart-app-plugin.md
@@ -0,0 +1,317 @@
+---
+id: quickstart-app-plugin
+title: Adding Custom Plugin to Existing Monorepo App
+---
+
+###### September 15th 2020 - v0.1.1-alpha.21
+
+
+
+> This document takes you through setting up a new plugin for your existing
+> monorepo with a _GitHub provider already setup_. If you don't have either of
+> those, you can clone
+> [simple-backstage-app](https://github.com/johnson-jesse/simple-backstage-app)
+> which this document builds on.
+>
+> This document does not cover authoring a plugin for sharing with the Backstage
+> community. That will have to be a later discussion.
+>
+> We start with a skeleton plugin install. And after verifying its
+> functionality, extend the Sidebar to make our life easy. Finally, we add
+> custom code to display GitHub repository information.
+>
+> This document assumes you have NodeJS 12 active along with Yarn. Please note,
+> that at the time of this writing, the current version is 0.1.1-alpha.21. This
+> guide can still be used with future versions, just, verify as you go. If you
+> run into issues, you can compare your setup with mine here >
+> [simple-backstage-app-plugin](https://github.com/johnson-jesse/simple-backstage-app-plugin).
+
+# The Skeleton Plugin
+
+1. Start by using the built in creator. From the terminal and root of your
+ project run: `yarn create-plugin`
+1. Enter a plugin ID. I used `github-playground`
+1. When the process finishes, let's start the backend:
+ `yarn --cwd packages/backend start`
+1. If you see errors starting, refer to
+ [Auth Configuration](https://backstage.io/docs/tutorials/quickstart-app-auth#the-auth-configuration)
+ for more information on environment variables.
+1. And now the frontend, from a new terminal window and the root of your
+ project: `yarn start`
+1. As usual, a browser window should popup loading the App.
+1. Now manually navigate to our plugin page from your browser:
+ `http://localhost:3000/github-playground`
+1. You should see successful verbiage for this endpoint,
+ `Welcome to github-playground!`
+
+# The Shortcut
+
+Let's add a shortcut.
+
+1. Open and modify `root: packages > app > src > sidebar.tsx` with the
+ following:
+
+```tsx
+import GitHubIcon from '@material-ui/icons/GitHub';
+...
+
+```
+
+Simple! The App will reload with your changes automatically. You should now see
+a github icon displayed in the sidebar. Clicking that will link to our new
+plugin. And now, the API fun begins.
+
+# The Identity
+
+Our first modification will be to extract information from the Identity API.
+
+1. Start by opening
+ `root: plugins > github-playground > src > components > ExampleComponent > ExampleComponent.tsx`
+1. Add two new imports
+
+```tsx
+// Add identityApiRef to the list of imported from core
+import { identityApiRef } from '@backstage/core';
+import { useApi } from '@backstage/core-api';
+```
+
+3. Adjust the ExampleComponent from inline to block
+
+_from inline:_
+
+```tsx
+const ExampleComponent: FC<{}> = () => ( ... )
+```
+
+_to block:_
+
+```tsx
+const ExampleComponent: FC<{}> = () => {
+
+ return (
+ ...
+ )
+}
+```
+
+4. Now add our hook and const data before the return statement
+
+```tsx
+// our API hook
+const identityApi = useApi(identityApiRef);
+
+// data to use
+const userId = identityApi.getUserId();
+const profile = identityApi.getProfile();
+```
+
+5. Finally, update the InfoCard's jsx to use our new data
+
+```tsx
+
+
+ {`${profile.displayName} | ${profile.email}`}
+
+
+```
+
+If everything is saved, you should see your name, id, and email on the
+github-playground page. Our data accessed is synchronous. So we just grab and
+go.
+
+https://github.com/spotify/backstage/tree/master/contrib
+
+6. Here is the entire file for reference
+ [ExampleComponent.tsx](https://github.com/spotify/backstage/tree/master/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md)
+
+# The Wipe
+
+The last file we will touch is ExampleFetchComponent. Because of the number of
+changes, let's start by wiping this component clean.
+
+1. Start by opening
+ `root: plugins > github-playground > src > components > ExampleFetchComponent > ExampleFetchComponent.tsx`
+1. Replace everything in the file with the following:
+
+```tsx
+import React, { FC } from 'react';
+import { useAsync } from 'react-use';
+import Alert from '@material-ui/lab/Alert';
+import {
+ Table,
+ TableColumn,
+ Progress,
+ githubAuthApiRef,
+} from '@backstage/core';
+import { useApi } from '@backstage/core-api';
+import { graphql } from '@octokit/graphql';
+
+const ExampleFetchComponent: FC<{}> = () => {
+ return Nothing to see yet
;
+};
+
+export default ExampleFetchComponent;
+```
+
+3. Save that and ensure you see no errors. Comment out the unused imports if
+ your linter gets in the way.
+
+###### We will add a lot to this file for the sake of ease. Please don't do this in productional code!
+
+# The Graph Model
+
+GitHub has a graphql API available for interacting. Let's start by adding our
+basic repository query
+
+1. Add the query const statement outside ExampleFetchComponent
+
+```tsx
+const query = `{
+ viewer {
+ repositories(first: 100) {
+ totalCount
+ nodes {
+ name
+ createdAt
+ description
+ diskUsage
+ isFork
+ }
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ }
+ }
+}`;
+```
+
+2. Using this structure as a guide, we will break our query into type parts
+3. Add the following outside of ExampleFetchComponent
+
+```tsx
+type Node = {
+ name: string;
+ createdAt: string;
+ description: string;
+ diskUsage: number;
+ isFork: boolean;
+};
+
+type Viewer = {
+ repositories: {
+ totalCount: number;
+ nodes: Node[];
+ pageInfo: {
+ endCursor: string;
+ hasNextPage: boolean;
+ };
+ };
+};
+```
+
+# The Tabel Model
+
+Using Backstage's own component library, let's define a custom table. This
+component will get used if we have data to display.
+
+1. Add the following outside of ExampleFetchComponent
+
+```tsx
+type DenseTableProps = {
+ viewer: Viewer;
+};
+
+export const DenseTable: FC = ({ viewer }) => {
+ const columns: TableColumn[] = [
+ { title: 'Name', field: 'name' },
+ { title: 'Created', field: 'createdAt' },
+ { title: 'Description', field: 'description' },
+ { title: 'Disk Usage', field: 'diskUsage' },
+ { title: 'Fork', field: 'isFork' },
+ ];
+
+ return (
+
+ );
+};
+```
+
+# The Fetch
+
+We're ready to flush out our fetch component
+
+1. Add our api hook inside ExampleFetchComponent
+
+```tsx
+const auth = useApi(githubAuthApiRef);
+```
+
+2. The access token we need to make our GitHub request and the request itself is
+ obtained in an asynchronous manner.
+3. Add the useAsync block inside the ExampleFetchComponent
+
+```tsx
+const { value, loading, error } = useAsync(async (): Promise => {
+ const token = await auth.getAccessToken();
+
+ const gqlEndpoint = graphql.defaults({
+ // Uncomment baseUrl if using enterprise
+ // baseUrl: 'https://github.MY-BIZ.com/api',
+ headers: {
+ authorization: `token ${token}`,
+ },
+ });
+ const { viewer } = await gqlEndpoint(query);
+ return viewer;
+}, []);
+```
+
+4. The resolved data is conveniently destructured with `value` containing our
+ Viewer type. `loading` as a boolean, self explanatory. And `error` which is
+ present only if necessary. So let's use those as the first 3 of 4 multi
+ return statements.
+5. Add the _if return_ blocks below our async block
+
+```tsx
+if (loading) return ;
+if (error) return {error.message} ;
+if (value && value.repositories) return ;
+```
+
+6. The third line here utilizes our custom table accepting our Viewer type.
+7. Finally, we add our _else return_ block to catch any other scenarios.
+
+```tsx
+return (
+
+);
+```
+
+8. After saving that, and given we don't have any errors, you should see a table
+ with basic information on your repositories.
+9. Here is the entire file for reference
+ [ExampleFetchComponent.tsx](https://github.com/spotify/backstage/tree/master/contrib/docs/tutorials/quickstart-app-plugin/ExampleFetchComponent.md)
+10. We finished! You should see your own GitHub repository's information
+ displayed in a basic table. If you run into issues, you can compare the repo
+ that backs this document,
+ [simple-backstage-app-plugin](https://github.com/johnson-jesse/simple-backstage-app-plugin)
+
+# Where to go from here
+
+> Break apart ExampleFetchComponent into smaller logical parts contained in
+> their own files. Rename your components to something other than ExampleXxx.
+>
+> You might be real proud of a plugin you develop. Follow this next tutorial for
+> an in-depth look at publishing and including that for the entire Backstage
+> community. [TODO](#).
diff --git a/lerna.json b/lerna.json
index 63e4701ce3..d43d4ff160 100644
--- a/lerna.json
+++ b/lerna.json
@@ -2,5 +2,5 @@
"packages": ["packages/*", "plugins/*"],
"npmClient": "yarn",
"useWorkspaces": true,
- "version": "0.1.1-alpha.20"
+ "version": "0.1.1-alpha.23"
}
diff --git a/microsite/blog/2020-08-05-announcing-backstage-software-templates.md b/microsite/blog/2020-08-05-announcing-backstage-software-templates.md
index 51c1da2885..81d495e1c3 100644
--- a/microsite/blog/2020-08-05-announcing-backstage-software-templates.md
+++ b/microsite/blog/2020-08-05-announcing-backstage-software-templates.md
@@ -39,7 +39,7 @@ You can customize Backstage Software Templates to fit your organization’s stan
## Getting started
-The sample Software Templates are available under `/create`. If you're setting up Backstage for the first time, follow [Getting Started with Backstage](https://backstage.io/docs/getting-started/) and go to `http://localhost:3000/create`. If you’ve already been running Backstage locally, run the command `yarn lerna run mock-data` to load the new sample templates into the Service Catalog first.
+The sample Software Templates are available under `/create`. If you're setting up Backstage for the first time, follow [Getting Started with Backstage](https://backstage.io/docs/getting-started/) and go to `http://localhost:3000/create`.

diff --git a/microsite/blog/2020-09-23-backstage-cncf-sandbox.md b/microsite/blog/2020-09-23-backstage-cncf-sandbox.md
new file mode 100644
index 0000000000..bcfd7c6245
--- /dev/null
+++ b/microsite/blog/2020-09-23-backstage-cncf-sandbox.md
@@ -0,0 +1,21 @@
+---
+title: Backstage has been accepted into the CNCF Sandbox
+author: Stefan Ålund
+authorURL: https://twitter.com/stalund
+---
+
+**TL;DR** The Cloud Native Computing Foundation (CNCF) announced that Backstage can begin incubating as an early stage project in the [CNCF Sandbox](https://www.cncf.io/sandbox-projects/). Released open source in March, the platform is built around an advanced service catalog and is designed to streamline software development from end to end.
+
+
+
+
+
+Backstage garnered quite a bit of interest from developers and organizations when it was first announced, and community interest continues to grow as plugins and new features are added with the open source community. We released the open source version of Backstage ‘early’. That was intentional. Because even though we’ve been using Backstage internally for years, we wanted the open source version to be developed with input and contributions from the community. And that’s exactly the product that’s going into the [CNCF Sandbox](https://www.cncf.io/sandbox-projects/) today.
+
+Backstage’s ability to simplify tooling and standardize engineering practices has attracted interest from other major tech companies, as well as airlines, auto manufacturers, investment firms, and global retailers. We know that Backstage solves a problem — infrastructure complexity — that’s common to a lot of large and growing companies today. But different companies work differently, use particular toolsets, and have unique use cases. By making Backstage open source, we can build it with people working inside a variety of engineering organizations all over the world. It makes for a better product that serves a wider group of users (beyond that of Spotify’s) and their needs.
+
+The Backstage community is healthy and growing quickly. Over [130 people](https://github.com/spotify/backstage/graphs/contributors) have contributed to the project, and roughly 40% of pull requests are now coming from external, non-Spotify, contributors. With companies now deciding to [adopt Backstage](https://github.com/spotify/backstage/blob/master/ADOPTERS.md) we are also seeing a shift in the kinds of contributions we are getting from the community. It is truly amazing to see contributions to core parts of the platform as well as significant functionality additions through working [plugins](https://backstage.io/plugins).
+
+We’re excited to embark on this journey with the CNCF community. There’s so much great tech being built here, and it’s about time we share it to build even greater products, together. Entering into the CNCF Sandbox is just the first step. We are committed to working with the community to bring Backstage through the Incubation step, and finally all the way to becoming a Graduated, top-level project.
+
+Thanks to everyone for your support so far. We hope you [join us](https://mailchi.mp/spotify/backstage-community) in this next chapter of Backstage's journey. If you have questions or feedback, feel free to [email](mailto:alund@spotify.com) me directly.
diff --git a/microsite/blog/assets/cncf-sandbox/cncf.png b/microsite/blog/assets/cncf-sandbox/cncf.png
new file mode 100644
index 0000000000..624094b016
Binary files /dev/null and b/microsite/blog/assets/cncf-sandbox/cncf.png differ
diff --git a/microsite/package.json b/microsite/package.json
index 25598c18b4..734f1e4d49 100644
--- a/microsite/package.json
+++ b/microsite/package.json
@@ -13,7 +13,7 @@
"rename-version": "docusaurus-rename-version"
},
"devDependencies": {
- "docusaurus": "^2.0.0-alpha.61",
+ "docusaurus": "^2.0.0-alpha.64",
"js-yaml": "^3.14.0"
}
}
diff --git a/microsite/pages/en/index.js b/microsite/pages/en/index.js
index f595032bd0..6e649aa574 100644
--- a/microsite/pages/en/index.js
+++ b/microsite/pages/en/index.js
@@ -462,6 +462,19 @@ class Index extends React.Component {
Contribute
+
+
+
+
+ Backstage is a{' '}
+
+ Cloud Native Computing Foundation
+ {' '}
+ sandbox project
+
+
+
+
);
}
diff --git a/microsite/sidebars.json b/microsite/sidebars.json
index a3c11beeac..8fa4935a38 100644
--- a/microsite/sidebars.json
+++ b/microsite/sidebars.json
@@ -12,6 +12,7 @@
],
"Getting Started": [
"getting-started/index",
+ "getting-started/running-backstage-locally",
"getting-started/installation",
"getting-started/development-environment",
"getting-started/create-an-app",
@@ -39,8 +40,10 @@
"ids": [
"features/software-catalog/software-catalog-overview",
"features/software-catalog/installation",
+ "features/software-catalog/configuration",
"features/software-catalog/system-model",
"features/software-catalog/descriptor-format",
+ "features/software-catalog/well-known-annotations",
"features/software-catalog/extending-the-model",
"features/software-catalog/external-integrations",
"features/software-catalog/software-catalog-api"
@@ -61,11 +64,12 @@
},
{
"type": "subcategory",
- "label": "Docs-like-code",
+ "label": "TechDocs",
"ids": [
"features/techdocs/techdocs-overview",
"features/techdocs/getting-started",
"features/techdocs/concepts",
+ "features/techdocs/architecture",
"features/techdocs/creating-and-publishing",
"features/techdocs/faqs"
]
@@ -140,7 +144,11 @@
"ids": ["api/backend"]
}
],
- "Tutorials": ["tutorials/journey"],
+ "Tutorials": [
+ "tutorials/journey",
+ "tutorials/quickstart-app-auth",
+ "tutorials/quickstart-app-plugin"
+ ],
"Architecture Decision Records (ADRs)": [
"architecture-decisions/adrs-overview",
"architecture-decisions/adrs-adr001",
diff --git a/microsite/siteConfig.js b/microsite/siteConfig.js
index cc5ad89e09..d7a3bd67d0 100644
--- a/microsite/siteConfig.js
+++ b/microsite/siteConfig.js
@@ -67,8 +67,9 @@ const siteConfig = {
primaryColor: '#36BAA2',
secondaryColor: '#121212',
textColor: '#FFFFFF',
- navigatorTitleTextColor: '#9e9e9e',
- navigatorItemTextColor: '#616161',
+ navigatorTitleTextColor: '#e4e4e4',
+ navigatorItemTextColor: '#9e9e9e',
+ navGroupSubcategoryTitleColor: '#9e9e9e',
},
/* Colors for syntax highlighting */
@@ -93,8 +94,10 @@ const siteConfig = {
cleanUrl: true,
// Open Graph and Twitter card images.
- ogImage: 'img/logo-gradient-on-dark.svg',
- twitterImage: 'img/logo-gradient-on-dark.svg',
+ ogImage:
+ 'logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_04_Icon_Teal.png',
+ twitterImage:
+ 'logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_04_Icon_Teal.png',
// For sites with a sizable amount of content, set collapsible to true.
// Expand/collapse the links and subcategories under categories.
diff --git a/microsite/static/css/custom.css b/microsite/static/css/custom.css
index 5e078582cc..79b8fd9de4 100644
--- a/microsite/static/css/custom.css
+++ b/microsite/static/css/custom.css
@@ -113,6 +113,10 @@ td {
color: $navigatorTitleTextColor;
}
+.toc .toggleNav .navGroup .navGroupSubcategoryTitle {
+ color: $navGroupSubcategoryTitleColor;
+}
+
.toc .toggleNav ul li a,
.onPageNav a {
color: $navigatorItemTextColor;
@@ -203,6 +207,54 @@ td {
border-radius: 0.25rem;
}
+/*
+ * Fix for viewing Table of Contents bar on documentation
+ * and blog pages on smaller screens.
+ */
+@media only screen and (max-width: 1023px) {
+ /* Nav bar hides the docs toc bar */
+ .docMainWrapper {
+ margin-top: 4rem;
+ }
+
+ /* Toc bar does not have to be fixed */
+ .docsNavContainer {
+ position: unset;
+ width: 95vw;
+ z-index: 100;
+ margin-left: -2vw;
+ }
+
+ /* Toc bar does not have to be fixed when slider is active */
+ .docsSliderActive .toc .navBreadcrumb,
+ .tocActive .navBreadcrumb {
+ position: unset;
+ }
+
+ /* Fix unexpected width increase when toc is toggled */
+ .docsSliderActive .toc .navBreadcrumb,
+ .tocActive .navBreadcrumb {
+ width: inherit;
+ }
+
+ /* This pseudo-element stops toc toggle button to be clicked */
+ header.postHeader::before {
+ height: 2em !important;
+ margin-top: -2em !important;
+ }
+
+ /* This pseudo-element stops toc toggle button to be clicked */
+ #__docusaurus.postHeaderTitle::before {
+ height: 1em;
+ margin-top: -1em;
+ }
+
+ /* Useless button causing trouble */
+ .tocToggler {
+ display: none;
+ }
+}
+
/* content */
.postContainer blockquote {
color: $textColor;
@@ -288,17 +340,17 @@ code {
background: linear-gradient(70.44deg, #121212 75%, #5d817b 100%);
}
.bg-teal-top-right {
- background:
+ background:
/* linear-gradient(
178.64deg,
rgba(255, 255, 255, 0) 57.61%,
rgba(255, 255, 255, 0.17) 127.71%
- ), */
+ ), */
/* linear-gradient(
144.35deg,
rgba(98, 197, 179, 0) 56.68%,
rgba(98, 197, 179, 0.59) 109.25%
- ), */
+ ), */
/* linear-gradient(
192.29deg,
rgba(155, 240, 225, 0) 54.17%,
@@ -1018,3 +1070,16 @@ code {
margin: auto;
}
}
+
+.cncf-block {
+ padding-top: 40px;
+ text-align: center;
+}
+
+.cncf-logo {
+ background: center no-repeat url(../img/cncf-white.svg);
+ width: 100%;
+ height: 100px;
+ margin-bottom: 40px;
+ margin-top: 20px;
+}
diff --git a/microsite/static/img/cncf-color.svg b/microsite/static/img/cncf-color.svg
new file mode 100644
index 0000000000..12f7d3e48a
--- /dev/null
+++ b/microsite/static/img/cncf-color.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/microsite/static/img/cncf-white.svg b/microsite/static/img/cncf-white.svg
new file mode 100644
index 0000000000..d94aaf3249
--- /dev/null
+++ b/microsite/static/img/cncf-white.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/microsite/static/logo_assets/ai/Backstage Identity_Assets_Artwork_RGB.ai b/microsite/static/logo_assets/ai/Backstage_Identity_Assets_Artwork_RGB.ai
similarity index 100%
rename from microsite/static/logo_assets/ai/Backstage Identity_Assets_Artwork_RGB.ai
rename to microsite/static/logo_assets/ai/Backstage_Identity_Assets_Artwork_RGB.ai
diff --git a/microsite/static/logo_assets/jpeg/Backstage Identity_Assets_Artwork_RGB_05 Logo_Black.jpg b/microsite/static/logo_assets/jpeg/Backstage_Identity_Assets_Artwork_RGB_05 Logo_Black.jpg
similarity index 100%
rename from microsite/static/logo_assets/jpeg/Backstage Identity_Assets_Artwork_RGB_05 Logo_Black.jpg
rename to microsite/static/logo_assets/jpeg/Backstage_Identity_Assets_Artwork_RGB_05 Logo_Black.jpg
diff --git a/microsite/static/logo_assets/jpeg/Backstage Identity_Assets_Artwork_RGB_06 Icon_Black.jpg b/microsite/static/logo_assets/jpeg/Backstage_Identity_Assets_Artwork_RGB_06_Icon_Black.jpg
similarity index 100%
rename from microsite/static/logo_assets/jpeg/Backstage Identity_Assets_Artwork_RGB_06 Icon_Black.jpg
rename to microsite/static/logo_assets/jpeg/Backstage_Identity_Assets_Artwork_RGB_06_Icon_Black.jpg
diff --git a/microsite/static/logo_assets/pdf/Backstage Identity_Assets_Artwork_RGB.pdf b/microsite/static/logo_assets/pdf/Backstage_Identity_Assets_Artwork_RGB.pdf
similarity index 100%
rename from microsite/static/logo_assets/pdf/Backstage Identity_Assets_Artwork_RGB.pdf
rename to microsite/static/logo_assets/pdf/Backstage_Identity_Assets_Artwork_RGB.pdf
diff --git a/microsite/static/logo_assets/pdf/Individual/01 Logo_White.pdf b/microsite/static/logo_assets/pdf/Individual/01_Logo_White.pdf
similarity index 100%
rename from microsite/static/logo_assets/pdf/Individual/01 Logo_White.pdf
rename to microsite/static/logo_assets/pdf/Individual/01_Logo_White.pdf
diff --git a/microsite/static/logo_assets/pdf/Individual/02 Icon_White.pdf b/microsite/static/logo_assets/pdf/Individual/02_Icon_White.pdf
similarity index 100%
rename from microsite/static/logo_assets/pdf/Individual/02 Icon_White.pdf
rename to microsite/static/logo_assets/pdf/Individual/02_Icon_White.pdf
diff --git a/microsite/static/logo_assets/pdf/Individual/03 Logo_Teal.pdf b/microsite/static/logo_assets/pdf/Individual/03_Logo_Teal.pdf
similarity index 100%
rename from microsite/static/logo_assets/pdf/Individual/03 Logo_Teal.pdf
rename to microsite/static/logo_assets/pdf/Individual/03_Logo_Teal.pdf
diff --git a/microsite/static/logo_assets/pdf/Individual/04 Icon_Teal.pdf b/microsite/static/logo_assets/pdf/Individual/04_Icon_Teal.pdf
similarity index 100%
rename from microsite/static/logo_assets/pdf/Individual/04 Icon_Teal.pdf
rename to microsite/static/logo_assets/pdf/Individual/04_Icon_Teal.pdf
diff --git a/microsite/static/logo_assets/pdf/Individual/05 Logo_Black.pdf b/microsite/static/logo_assets/pdf/Individual/05_Logo_Black.pdf
similarity index 100%
rename from microsite/static/logo_assets/pdf/Individual/05 Logo_Black.pdf
rename to microsite/static/logo_assets/pdf/Individual/05_Logo_Black.pdf
diff --git a/microsite/static/logo_assets/pdf/Individual/06 Icon_Black.pdf b/microsite/static/logo_assets/pdf/Individual/06_Icon_Black.pdf
similarity index 100%
rename from microsite/static/logo_assets/pdf/Individual/06 Icon_Black.pdf
rename to microsite/static/logo_assets/pdf/Individual/06_Icon_Black.pdf
diff --git a/microsite/static/logo_assets/pdf/Individual/07 Large Icon_Gradient.pdf b/microsite/static/logo_assets/pdf/Individual/07_Large Icon_Gradient.pdf
similarity index 100%
rename from microsite/static/logo_assets/pdf/Individual/07 Large Icon_Gradient.pdf
rename to microsite/static/logo_assets/pdf/Individual/07_Large Icon_Gradient.pdf
diff --git a/microsite/static/logo_assets/png/Backstage Identity_Assets_Artwork_RGB_01 Logo_White.png b/microsite/static/logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_01_Logo_White.png
similarity index 100%
rename from microsite/static/logo_assets/png/Backstage Identity_Assets_Artwork_RGB_01 Logo_White.png
rename to microsite/static/logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_01_Logo_White.png
diff --git a/microsite/static/logo_assets/png/Backstage Identity_Assets_Artwork_RGB_02 Icon_White.png b/microsite/static/logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_02_Icon_White.png
similarity index 100%
rename from microsite/static/logo_assets/png/Backstage Identity_Assets_Artwork_RGB_02 Icon_White.png
rename to microsite/static/logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_02_Icon_White.png
diff --git a/microsite/static/logo_assets/png/Backstage Identity_Assets_Artwork_RGB_03 Logo_Teal.png b/microsite/static/logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_03_Logo_Teal.png
similarity index 100%
rename from microsite/static/logo_assets/png/Backstage Identity_Assets_Artwork_RGB_03 Logo_Teal.png
rename to microsite/static/logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_03_Logo_Teal.png
diff --git a/microsite/static/logo_assets/png/Backstage Identity_Assets_Artwork_RGB_04 Icon_Teal.png b/microsite/static/logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_04_Icon_Teal.png
similarity index 100%
rename from microsite/static/logo_assets/png/Backstage Identity_Assets_Artwork_RGB_04 Icon_Teal.png
rename to microsite/static/logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_04_Icon_Teal.png
diff --git a/microsite/static/logo_assets/png/Backstage Identity_Assets_Artwork_RGB_05 Logo_Black.png b/microsite/static/logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_05_Logo_Black.png
similarity index 100%
rename from microsite/static/logo_assets/png/Backstage Identity_Assets_Artwork_RGB_05 Logo_Black.png
rename to microsite/static/logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_05_Logo_Black.png
diff --git a/microsite/static/logo_assets/png/Backstage Identity_Assets_Artwork_RGB_06 Icon_Black.png b/microsite/static/logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_06_Icon_Black.png
similarity index 100%
rename from microsite/static/logo_assets/png/Backstage Identity_Assets_Artwork_RGB_06 Icon_Black.png
rename to microsite/static/logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_06_Icon_Black.png
diff --git a/microsite/static/logo_assets/png/Backstage Identity_Assets_Artwork_RGB_07 Large Icon_Gradient.png b/microsite/static/logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_07_Large_Icon_Gradient.png
similarity index 100%
rename from microsite/static/logo_assets/png/Backstage Identity_Assets_Artwork_RGB_07 Large Icon_Gradient.png
rename to microsite/static/logo_assets/png/Backstage_Identity_Assets_Artwork_RGB_07_Large_Icon_Gradient.png
diff --git a/microsite/yarn.lock b/microsite/yarn.lock
index 422959d3fd..84b5d50d60 100644
--- a/microsite/yarn.lock
+++ b/microsite/yarn.lock
@@ -2228,10 +2228,10 @@ dir-glob@2.0.0:
arrify "^1.0.1"
path-type "^3.0.0"
-docusaurus@^2.0.0-alpha.61:
- version "2.0.0-alpha.63"
- resolved "https://registry.npmjs.org/docusaurus/-/docusaurus-2.0.0-alpha.63.tgz#40402d47b18c42b62e93beb78ce06e000cb88312"
- integrity sha512-R19pAqcTemJMt7Qykd7ogB2i6R86vbE4/vA/l5/Uuh7xg7ixSKOVZ5M7d9uSoX5jCALOwEyp5iJdleznXUVwAw==
+docusaurus@^2.0.0-alpha.64:
+ version "2.0.0-alpha.64"
+ resolved "https://registry.yarnpkg.com/docusaurus/-/docusaurus-2.0.0-alpha.64.tgz#7833960e9d338403894a27b79058aa4076a676d3"
+ integrity sha512-ARCx0GwAvc5qx7AHvRVZidZuoDTfaaGXzgmkU23NahU6jzO/aK2Q1bH8IKNEQ5C2JuDerQ/hHDh80N20ijk82g==
dependencies:
"@babel/core" "^7.9.0"
"@babel/plugin-proposal-class-properties" "^7.8.3"
diff --git a/mkdocs.yml b/mkdocs.yml
index bcf4969892..ba607f8a6e 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -12,7 +12,8 @@ nav:
- Strategies for adopting: 'overview/adopting.md'
- Logo assets: 'overview/logos.md'
- Getting started:
- - Running Backstage locally: 'getting-started/index.md'
+ - Getting Started: 'getting-started/index.md'
+ - Running Backstage locally: 'getting-started/running-backstage-locally.md'
- Installation: 'getting-started/installation.md'
- Local development: 'getting-started/development-environment.md'
- Demo deployment: https://backstage-demo.roadie.io
@@ -27,9 +28,11 @@ nav:
- Features:
- Software Catalog:
- Overview: 'features/software-catalog/index.md'
+ - Installation: 'features/software-catalog/installation.md'
+ - Configuration: 'features/software-catalog/configuration.md'
- System model: 'features/software-catalog/system-model.md'
- YAML File Format: 'features/software-catalog/descriptor-format.md'
- - Configuration: 'features/software-catalog/configuration.md'
+ - Well-known Annotations: 'features/software-catalog/well-known-annotations.md'
- Extending the model: 'features/software-catalog/extending-the-model.md'
- External integrations: 'features/software-catalog/external-integrations.md'
- API: 'features/software-catalog/api.md'
@@ -42,10 +45,11 @@ nav:
- Create your own Templater: 'features/software-templates/extending/create-your-own-templater.md'
- Create your own Publisher: 'features/software-templates/extending/create-your-own-publisher.md'
- Create your own Preparer: 'features/software-templates/extending/create-your-own-preparer.md'
- - Docs-like-code:
+ - TechDocs:
- Overview: 'features/techdocs/README.md'
- Getting Started: 'features/techdocs/getting-started.md'
- Concepts: 'features/techdocs/concepts.md'
+ - TechDocs Architecture: 'features/techdocs/architecture.md'
- Creating and Publishing Documentation: 'features/techdocs/creating-and-publishing.md'
- FAQ: 'features/techdocs/FAQ.md'
- Plugins:
diff --git a/package.json b/package.json
index c64b7f6951..3a77fe6880 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,7 @@
"docgen": "lerna run docgen",
"docker-build:app": "yarn workspace example-app build && docker build . -t spotify/backstage",
"docker-build": "yarn tsc && yarn workspace example-backend build-image",
- "create-plugin": "backstage-cli create-plugin",
+ "create-plugin": "backstage-cli create-plugin --scope backstage --no-private",
"remove-plugin": "backstage-cli remove-plugin",
"release": "if [ \"$(git symbolic-ref --short HEAD)\" = master ]; then echo \"don't try to release master\"; exit 1; else lerna version --no-push --force-publish; fi",
"prettier:check": "prettier --check .",
@@ -37,6 +37,7 @@
},
"version": "1.0.0",
"devDependencies": {
+ "@changesets/cli": "2.10.2",
"@spotify/eslint-config-oss": "^1.0.1",
"@spotify/prettier-config": "^8.0.0",
"concurrently": "^5.2.0",
diff --git a/packages/app/package.json b/packages/app/package.json
index bf0f340a40..77b1882b47 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,32 +1,33 @@
{
"name": "example-app",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"private": true,
"bundled": true,
"dependencies": {
- "@backstage/cli": "^0.1.1-alpha.21",
- "@backstage/catalog-model": "^0.1.1-alpha.21",
- "@backstage/core": "^0.1.1-alpha.21",
- "@backstage/plugin-api-docs": "^0.1.1-alpha.21",
- "@backstage/plugin-catalog": "^0.1.1-alpha.21",
- "@backstage/plugin-circleci": "^0.1.1-alpha.21",
- "@backstage/plugin-explore": "^0.1.1-alpha.21",
- "@backstage/plugin-gcp-projects": "^0.1.1-alpha.21",
- "@backstage/plugin-github-actions": "^0.1.1-alpha.21",
- "@backstage/plugin-gitops-profiles": "^0.1.1-alpha.21",
- "@backstage/plugin-graphiql": "^0.1.1-alpha.21",
- "@backstage/plugin-jenkins": "^0.1.1-alpha.21",
- "@backstage/plugin-lighthouse": "^0.1.1-alpha.21",
- "@backstage/plugin-newrelic": "^0.1.1-alpha.21",
- "@backstage/plugin-register-component": "^0.1.1-alpha.21",
- "@backstage/plugin-rollbar": "^0.1.1-alpha.21",
- "@backstage/plugin-scaffolder": "^0.1.1-alpha.21",
- "@backstage/plugin-sentry": "^0.1.1-alpha.21",
- "@backstage/plugin-tech-radar": "^0.1.1-alpha.21",
- "@backstage/plugin-techdocs": "^0.1.1-alpha.21",
- "@backstage/plugin-welcome": "^0.1.1-alpha.21",
- "@backstage/test-utils": "^0.1.1-alpha.21",
- "@backstage/theme": "^0.1.1-alpha.21",
+ "@backstage/catalog-model": "^0.1.1-alpha.23",
+ "@backstage/cli": "^0.1.1-alpha.23",
+ "@backstage/core": "^0.1.1-alpha.23",
+ "@backstage/plugin-api-docs": "^0.1.1-alpha.23",
+ "@backstage/plugin-catalog": "^0.1.1-alpha.23",
+ "@backstage/plugin-circleci": "^0.1.1-alpha.23",
+ "@backstage/plugin-explore": "^0.1.1-alpha.23",
+ "@backstage/plugin-gcp-projects": "^0.1.1-alpha.23",
+ "@backstage/plugin-github-actions": "^0.1.1-alpha.23",
+ "@backstage/plugin-gitops-profiles": "^0.1.1-alpha.23",
+ "@backstage/plugin-graphiql": "^0.1.1-alpha.23",
+ "@backstage/plugin-jenkins": "^0.1.1-alpha.23",
+ "@backstage/plugin-kubernetes": "^0.1.1-alpha.23",
+ "@backstage/plugin-lighthouse": "^0.1.1-alpha.23",
+ "@backstage/plugin-newrelic": "^0.1.1-alpha.23",
+ "@backstage/plugin-register-component": "^0.1.1-alpha.23",
+ "@backstage/plugin-rollbar": "^0.1.1-alpha.23",
+ "@backstage/plugin-scaffolder": "^0.1.1-alpha.23",
+ "@backstage/plugin-sentry": "^0.1.1-alpha.23",
+ "@backstage/plugin-tech-radar": "^0.1.1-alpha.23",
+ "@backstage/plugin-techdocs": "^0.1.1-alpha.23",
+ "@backstage/plugin-welcome": "^0.1.1-alpha.23",
+ "@backstage/test-utils": "^0.1.1-alpha.23",
+ "@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@octokit/rest": "^18.0.0",
diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx
index e91504d619..03e24cf4ae 100644
--- a/packages/app/src/components/Root/Root.tsx
+++ b/packages/app/src/components/Root/Root.tsx
@@ -35,8 +35,6 @@ import {
SidebarSearchField,
SidebarSpace,
SidebarUserSettings,
- SidebarThemeToggle,
- SidebarPinButton,
DefaultProviderSettings,
} from '@backstage/core';
import { NavLink } from 'react-router-dom';
@@ -103,9 +101,7 @@ const Root: FC<{}> = ({ children }) => (
/>
-
} />
-
{children}
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index 257fde3c24..d96fe9841f 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -17,6 +17,11 @@ import {
Router as GitHubActionsRouter,
isPluginApplicableToEntity as isGitHubActionsAvailable,
} from '@backstage/plugin-github-actions';
+import {
+ Router as JenkinsRouter,
+ isPluginApplicableToEntity as isJenkinsAvailable,
+ LatestRunCard as JenkinsLatestRunCard,
+} from '@backstage/plugin-jenkins';
import {
Router as CircleCIRouter,
isPluginApplicableToEntity as isCircleCIAvailable,
@@ -24,6 +29,7 @@ import {
import { Router as ApiDocsRouter } from '@backstage/plugin-api-docs';
import { Router as SentryRouter } from '@backstage/plugin-sentry';
import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs';
+import { Router as KubernetesRouter } from '@backstage/plugin-kubernetes';
import React from 'react';
import {
AboutCard,
@@ -38,6 +44,8 @@ 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 isJenkinsAvailable(entity):
+ return ;
case isGitHubActionsAvailable(entity):
return ;
case isCircleCIAvailable(entity):
@@ -57,6 +65,11 @@ const OverviewContent = ({ entity }: { entity: Entity }) => (
+ {isJenkinsAvailable(entity) && (
+
+
+
+ )}
);
@@ -87,6 +100,11 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
title="Docs"
element={ }
/>
+ }
+ />
);
@@ -112,6 +130,11 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
title="Docs"
element={ }
/>
+ }
+ />
);
const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts
index b36d0323e0..aa68bc5fad 100644
--- a/packages/app/src/plugins.ts
+++ b/packages/app/src/plugins.ts
@@ -33,3 +33,4 @@ export { plugin as Jenkins } from '@backstage/plugin-jenkins';
export { plugin as ApiDocs } from '@backstage/plugin-api-docs';
export { plugin as GithubPullRequests } from '@roadiehq/backstage-plugin-github-pull-requests';
export { plugin as GcpProjects } from '@backstage/plugin-gcp-projects';
+export { plugin as Kubernetes } from '@backstage/plugin-kubernetes';
diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json
index c9103375eb..72f047761d 100644
--- a/packages/backend-common/package.json
+++ b/packages/backend-common/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-common",
"description": "Common functionality library for Backstage backends",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -29,9 +29,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/cli-common": "^0.1.1-alpha.21",
- "@backstage/config": "^0.1.1-alpha.21",
- "@backstage/config-loader": "^0.1.1-alpha.21",
+ "@backstage/cli-common": "^0.1.1-alpha.23",
+ "@backstage/config": "^0.1.1-alpha.23",
+ "@backstage/config-loader": "^0.1.1-alpha.23",
"@types/cors": "^2.8.6",
"@types/express": "^4.17.6",
"compression": "^1.7.4",
@@ -42,12 +42,12 @@
"helmet": "^4.0.0",
"knex": "^0.21.1",
"lodash": "^4.17.15",
+ "logform": "^2.1.1",
"morgan": "^1.10.0",
"prom-client": "^12.0.0",
"selfsigned": "^1.10.7",
"stoppable": "^1.1.0",
- "winston": "^3.2.1",
- "logform": "^2.1.1"
+ "winston": "^3.2.1"
},
"peerDependencies": {
"pg-connection-string": "^2.3.0"
@@ -58,7 +58,7 @@
}
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.21",
+ "@backstage/cli": "^0.1.1-alpha.23",
"@types/compression": "^1.7.0",
"@types/http-errors": "^1.6.3",
"@types/morgan": "^1.9.0",
diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts
index 6f7500bb86..247eb593cc 100644
--- a/packages/backend-common/src/config.ts
+++ b/packages/backend-common/src/config.ts
@@ -23,7 +23,7 @@ import { loadConfig } from '@backstage/config-loader';
export async function loadBackendConfig() {
const paths = findPaths(__dirname);
const configs = await loadConfig({
- env: process.env.NODE_ENV,
+ env: process.env.NODE_ENV ?? 'development',
rootPaths: [paths.targetRoot, paths.targetDir],
shouldReadSecrets: true,
});
diff --git a/packages/backend/README.md b/packages/backend/README.md
index 860520f4b5..c45a0d28d9 100644
--- a/packages/backend/README.md
+++ b/packages/backend/README.md
@@ -43,21 +43,13 @@ The backend starts up on port 7000 per default.
## Populating The Catalog
-If you want to use the catalog functionality, you need to add so called locations
-to the backend. These are places where the backend can find some entity descriptor
-data to consume and serve.
+If you want to use the catalog functionality, you need to add so called
+locations to the backend. These are places where the backend can find some
+entity descriptor data to consume and serve. For more information, see
+[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog).
-To get started, you can issue the following after starting the backend, from inside
-the `plugins/catalog-backend` directory:
-
-```bash
-yarn mock-data
-```
-
-You should then start seeing data on `localhost:7000/catalog/entities`.
-
-The catalog currently runs in-memory only, so feel free to try it out, but it will
-need to be re-populated on next startup.
+For convenience we already include some statically configured example locations
+in `app-config.yaml` under `catalog.locations`. For local development you can override these in your own `app-config.local.yaml`.
## Authentication
diff --git a/packages/backend/package.json b/packages/backend/package.json
index d944ac6535..e1b8665b57 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "example-backend",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"private": true,
@@ -18,22 +18,24 @@
"migrate:create": "knex migrate:make -x ts"
},
"dependencies": {
- "@backstage/backend-common": "^0.1.1-alpha.21",
- "@backstage/catalog-model": "^0.1.1-alpha.21",
- "@backstage/config": "^0.1.1-alpha.21",
- "@backstage/plugin-app-backend": "^0.1.1-alpha.21",
- "@backstage/plugin-auth-backend": "^0.1.1-alpha.21",
- "@backstage/plugin-catalog-backend": "^0.1.1-alpha.21",
- "@backstage/plugin-graphql-backend": "^0.1.1-alpha.21",
- "@backstage/plugin-identity-backend": "^0.1.1-alpha.21",
- "@backstage/plugin-proxy-backend": "^0.1.1-alpha.21",
- "@backstage/plugin-rollbar-backend": "^0.1.1-alpha.21",
- "@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.21",
- "@backstage/plugin-sentry-backend": "^0.1.1-alpha.21",
- "@backstage/plugin-techdocs-backend": "^0.1.1-alpha.21",
+ "@backstage/backend-common": "^0.1.1-alpha.23",
+ "@backstage/catalog-model": "^0.1.1-alpha.23",
+ "@backstage/config": "^0.1.1-alpha.23",
+ "@backstage/plugin-app-backend": "^0.1.1-alpha.23",
+ "@backstage/plugin-auth-backend": "^0.1.1-alpha.23",
+ "@backstage/plugin-catalog-backend": "^0.1.1-alpha.23",
+ "@backstage/plugin-graphql-backend": "^0.1.1-alpha.23",
+ "@backstage/plugin-identity-backend": "^0.1.1-alpha.23",
+ "@backstage/plugin-kubernetes-backend": "^0.1.1-alpha.23",
+ "@backstage/plugin-proxy-backend": "^0.1.1-alpha.23",
+ "@backstage/plugin-rollbar-backend": "^0.1.1-alpha.23",
+ "@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.23",
+ "@backstage/plugin-sentry-backend": "^0.1.1-alpha.23",
+ "@backstage/plugin-techdocs-backend": "^0.1.1-alpha.23",
+ "@gitbeaker/node": "^23.5.0",
"@octokit/rest": "^18.0.0",
"dockerode": "^3.2.0",
- "example-app": "^0.1.1-alpha.21",
+ "example-app": "^0.1.1-alpha.23",
"express": "^4.17.1",
"knex": "^0.21.1",
"pg": "^8.3.0",
@@ -42,7 +44,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.21",
+ "@backstage/cli": "^0.1.1-alpha.23",
"@types/dockerode": "^2.5.32",
"@types/express": "^4.17.6",
"@types/express-serve-static-core": "^4.17.5",
diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts
index 0423047cf7..7c52175d76 100644
--- a/packages/backend/src/index.ts
+++ b/packages/backend/src/index.ts
@@ -34,6 +34,7 @@ import healthcheck from './plugins/healthcheck';
import auth from './plugins/auth';
import catalog from './plugins/catalog';
import identity from './plugins/identity';
+import kubernetes from './plugins/kubernetes';
import rollbar from './plugins/rollbar';
import scaffolder from './plugins/scaffolder';
import sentry from './plugins/sentry';
@@ -64,7 +65,6 @@ async function main() {
const configs = await loadBackendConfig();
const configReader = ConfigReader.fromConfigs(configs);
const createEnv = makeCreateEnv(configs);
-
const healthcheckEnv = useHotMemoize(module, () => createEnv('healthcheck'));
const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
@@ -74,6 +74,7 @@ async function main() {
const rollbarEnv = useHotMemoize(module, () => createEnv('rollbar'));
const sentryEnv = useHotMemoize(module, () => createEnv('sentry'));
const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
+ const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes'));
const graphqlEnv = useHotMemoize(module, () => createEnv('graphql'));
const appEnv = useHotMemoize(module, () => createEnv('app'));
@@ -87,6 +88,7 @@ async function main() {
.addRouter('/auth', await auth(authEnv))
.addRouter('/identity', await identity(identityEnv))
.addRouter('/techdocs', await techdocs(techdocsEnv))
+ .addRouter('/kubernetes', await kubernetes(kubernetesEnv))
.addRouter('/proxy', await proxy(proxyEnv, '/proxy'))
.addRouter('/graphql', await graphql(graphqlEnv))
.addRouter('', await app(appEnv));
diff --git a/packages/backend/src/plugins/graphql.ts b/packages/backend/src/plugins/graphql.ts
index 0f9b167d19..d52e28f9cb 100644
--- a/packages/backend/src/plugins/graphql.ts
+++ b/packages/backend/src/plugins/graphql.ts
@@ -30,10 +30,14 @@
*/
import { createRouter } from '@backstage/plugin-graphql-backend';
-import type { PluginEnvironment } from '../types';
+import { PluginEnvironment } from '../types';
-export default async function createPlugin({ logger }: PluginEnvironment) {
+export default async function createPlugin({
+ logger,
+ config,
+}: PluginEnvironment) {
return await createRouter({
logger,
+ config,
});
}
diff --git a/packages/backend/src/plugins/kubernetes.ts b/packages/backend/src/plugins/kubernetes.ts
new file mode 100644
index 0000000000..42b7722ff6
--- /dev/null
+++ b/packages/backend/src/plugins/kubernetes.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 { createRouter } from '@backstage/plugin-kubernetes-backend';
+import { PluginEnvironment } from '../types';
+
+export default async function createPlugin({ logger }: PluginEnvironment) {
+ return await createRouter({ logger });
+}
diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts
index a9e5f185ec..f902ee76f0 100644
--- a/packages/backend/src/plugins/scaffolder.ts
+++ b/packages/backend/src/plugins/scaffolder.ts
@@ -19,16 +19,24 @@ import {
createRouter,
FilePreparer,
GithubPreparer,
+ GitlabPreparer,
Preparers,
+ Publishers,
GithubPublisher,
+ GitlabPublisher,
CreateReactAppTemplater,
Templaters,
+ RepoVisibilityOptions,
} from '@backstage/plugin-scaffolder-backend';
import { Octokit } from '@octokit/rest';
+import { Gitlab } from '@gitbeaker/node';
import type { PluginEnvironment } from '../types';
import Docker from 'dockerode';
-export default async function createPlugin({ logger }: PluginEnvironment) {
+export default async function createPlugin({
+ logger,
+ config,
+}: PluginEnvironment) {
const cookiecutterTemplater = new CookieCutter();
const craTemplater = new CreateReactAppTemplater();
const templaters = new Templaters();
@@ -37,19 +45,77 @@ export default async function createPlugin({ logger }: PluginEnvironment) {
const filePreparer = new FilePreparer();
const githubPreparer = new GithubPreparer();
+ const gitlabPreparer = new GitlabPreparer(config);
const preparers = new Preparers();
preparers.register('file', filePreparer);
preparers.register('github', githubPreparer);
+ preparers.register('gitlab', gitlabPreparer);
+ preparers.register('gitlab/api', gitlabPreparer);
- const githubClient = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN });
- const publisher = new GithubPublisher({ client: githubClient });
+ const publishers = new Publishers();
+
+ const githubConfig = config.getOptionalConfig('scaffolder.github');
+
+ if (githubConfig) {
+ try {
+ const repoVisibility = githubConfig.getString(
+ 'visibility',
+ ) as RepoVisibilityOptions;
+
+ const githubToken = githubConfig.getString('token');
+ const githubClient = new Octokit({ auth: githubToken });
+ const githubPublisher = new GithubPublisher({
+ client: githubClient,
+ token: githubToken,
+ repoVisibility,
+ });
+ publishers.register('file', githubPublisher);
+ publishers.register('github', githubPublisher);
+ } catch (e) {
+ const providerName = 'github';
+ if (process.env.NODE_ENV !== 'development') {
+ throw new Error(
+ `Failed to initialize ${providerName} scaffolding provider, ${e.message}`,
+ );
+ }
+
+ logger.warn(
+ `Skipping ${providerName} scaffolding provider, ${e.message}`,
+ );
+ }
+ }
+
+ const gitLabConfig = config.getOptionalConfig('scaffolder.gitlab.api');
+ if (gitLabConfig) {
+ try {
+ const gitLabToken = gitLabConfig.getString('token');
+ const gitLabClient = new Gitlab({
+ host: gitLabConfig.getOptionalString('baseUrl'),
+ token: gitLabToken,
+ });
+ const gitLabPublisher = new GitlabPublisher(gitLabClient, gitLabToken);
+ publishers.register('gitlab', gitLabPublisher);
+ publishers.register('gitlab/api', gitLabPublisher);
+ } catch (e) {
+ const providerName = 'gitlab';
+ if (process.env.NODE_ENV !== 'development') {
+ throw new Error(
+ `Failed to initialize ${providerName} scaffolding provider, ${e.message}`,
+ );
+ }
+
+ logger.warn(
+ `Skipping ${providerName} scaffolding provider, ${e.message}`,
+ );
+ }
+ }
const dockerClient = new Docker();
return await createRouter({
preparers,
templaters,
- publisher,
+ publishers,
logger,
dockerClient,
});
diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts
index 7364cc4d83..58dca83b43 100644
--- a/packages/backend/src/plugins/techdocs.ts
+++ b/packages/backend/src/plugins/techdocs.ts
@@ -31,7 +31,7 @@ export default async function createPlugin({
config,
}: PluginEnvironment) {
const generators = new Generators();
- const techdocsGenerator = new TechdocsGenerator(logger);
+ const techdocsGenerator = new TechdocsGenerator(logger, config);
generators.register('techdocs', techdocsGenerator);
const preparers = new Preparers();
diff --git a/packages/catalog-model/examples/all-apis.yaml b/packages/catalog-model/examples/all-apis.yaml
new file mode 100644
index 0000000000..33b000d1ae
--- /dev/null
+++ b/packages/catalog-model/examples/all-apis.yaml
@@ -0,0 +1,10 @@
+apiVersion: backstage.io/v1alpha1
+kind: Location
+metadata:
+ name: example-apis
+ description: A collection of all Backstage example APIs
+spec:
+ type: github
+ targets:
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/hello-world-api.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/streetlights-api.yaml
diff --git a/packages/catalog-model/examples/all-components.yaml b/packages/catalog-model/examples/all-components.yaml
new file mode 100644
index 0000000000..cad4eee044
--- /dev/null
+++ b/packages/catalog-model/examples/all-components.yaml
@@ -0,0 +1,16 @@
+apiVersion: backstage.io/v1alpha1
+kind: Location
+metadata:
+ name: example-components
+ description: A collection of all Backstage example components
+spec:
+ type: github
+ targets:
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/artist-lookup-component.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/playback-order-component.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/podcast-api-component.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/queue-proxy-component.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/searcher-component.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/playback-lib-component.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/www-artist-component.yaml
+ - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/shuffle-api-component.yaml
diff --git a/packages/catalog-model/examples/hello-world-api.yaml b/packages/catalog-model/examples/hello-world-api.yaml
index 298b0cb835..659c48adfb 100644
--- a/packages/catalog-model/examples/hello-world-api.yaml
+++ b/packages/catalog-model/examples/hello-world-api.yaml
@@ -5,6 +5,8 @@ metadata:
description: Hello World example for gRPC
spec:
type: grpc
+ lifecycle: deprecated
+ owner: grpc@example.com
definition: |
// Copyright 2015 gRPC authors.
//
diff --git a/packages/catalog-model/examples/petstore-api.yaml b/packages/catalog-model/examples/petstore-api.yaml
index 01953a4a97..c8ce576cce 100644
--- a/packages/catalog-model/examples/petstore-api.yaml
+++ b/packages/catalog-model/examples/petstore-api.yaml
@@ -3,8 +3,13 @@ kind: API
metadata:
name: petstore
description: The petstore API
+ tags:
+ - store
+ - rest
spec:
type: openapi
+ lifecycle: experimental
+ owner: pets@example.com
definition: |
openapi: "3.0.0"
info:
diff --git a/packages/catalog-model/examples/playback-order-component.yaml b/packages/catalog-model/examples/playback-order-component.yaml
index 4f93b65edf..3e46953928 100644
--- a/packages/catalog-model/examples/playback-order-component.yaml
+++ b/packages/catalog-model/examples/playback-order-component.yaml
@@ -3,6 +3,9 @@ kind: Component
metadata:
name: playback-order
description: Playback Order
+ tags:
+ - java
+ - playback
spec:
type: service
lifecycle: production
diff --git a/packages/catalog-model/examples/streetlights-api.yaml b/packages/catalog-model/examples/streetlights-api.yaml
index 4aeef993bb..d53b05fc2d 100644
--- a/packages/catalog-model/examples/streetlights-api.yaml
+++ b/packages/catalog-model/examples/streetlights-api.yaml
@@ -4,9 +4,11 @@ metadata:
name: streetlights
description: The Smartylighting Streetlights API allows you to remotely manage the city lights.
tags:
- - unstable
+ - mqtt
spec:
type: asyncapi
+ lifecycle: production
+ owner: streetlights@example.com
definition: |
asyncapi: 2.0.0
info:
diff --git a/packages/catalog-model/examples/swapi-graphql.yaml b/packages/catalog-model/examples/swapi-graphql.yaml
new file mode 100644
index 0000000000..152d9c0afa
--- /dev/null
+++ b/packages/catalog-model/examples/swapi-graphql.yaml
@@ -0,0 +1,1174 @@
+apiVersion: backstage.io/v1alpha1
+kind: API
+metadata:
+ name: starwars-graphql
+ description: SWAPI GraphQL Schema
+spec:
+ type: graphql
+ definition: |
+ schema {
+ query: Root
+ }
+
+ """A single film."""
+ type Film implements Node {
+ """The title of this film."""
+ title: String
+
+ """The episode number of this film."""
+ episodeID: Int
+
+ """The opening paragraphs at the beginning of this film."""
+ openingCrawl: String
+
+ """The name of the director of this film."""
+ director: String
+
+ """The name(s) of the producer(s) of this film."""
+ producers: [String]
+
+ """The ISO 8601 date format of film release at original creator country."""
+ releaseDate: String
+ speciesConnection(after: String, first: Int, before: String, last: Int): FilmSpeciesConnection
+ starshipConnection(after: String, first: Int, before: String, last: Int): FilmStarshipsConnection
+ vehicleConnection(after: String, first: Int, before: String, last: Int): FilmVehiclesConnection
+ characterConnection(after: String, first: Int, before: String, last: Int): FilmCharactersConnection
+ planetConnection(after: String, first: Int, before: String, last: Int): FilmPlanetsConnection
+
+ """The ISO 8601 date format of the time that this resource was created."""
+ created: String
+
+ """The ISO 8601 date format of the time that this resource was edited."""
+ edited: String
+
+ """The ID of an object"""
+ id: ID!
+ }
+
+ """A connection to a list of items."""
+ type FilmCharactersConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [FilmCharactersEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ characters: [Person]
+ }
+
+ """An edge in a connection."""
+ type FilmCharactersEdge {
+ """The item at the end of the edge"""
+ node: Person
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A connection to a list of items."""
+ type FilmPlanetsConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [FilmPlanetsEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ planets: [Planet]
+ }
+
+ """An edge in a connection."""
+ type FilmPlanetsEdge {
+ """The item at the end of the edge"""
+ node: Planet
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A connection to a list of items."""
+ type FilmsConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [FilmsEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ films: [Film]
+ }
+
+ """An edge in a connection."""
+ type FilmsEdge {
+ """The item at the end of the edge"""
+ node: Film
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A connection to a list of items."""
+ type FilmSpeciesConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [FilmSpeciesEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ species: [Species]
+ }
+
+ """An edge in a connection."""
+ type FilmSpeciesEdge {
+ """The item at the end of the edge"""
+ node: Species
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A connection to a list of items."""
+ type FilmStarshipsConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [FilmStarshipsEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ starships: [Starship]
+ }
+
+ """An edge in a connection."""
+ type FilmStarshipsEdge {
+ """The item at the end of the edge"""
+ node: Starship
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A connection to a list of items."""
+ type FilmVehiclesConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [FilmVehiclesEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ vehicles: [Vehicle]
+ }
+
+ """An edge in a connection."""
+ type FilmVehiclesEdge {
+ """The item at the end of the edge"""
+ node: Vehicle
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """An object with an ID"""
+ interface Node {
+ """The id of the object."""
+ id: ID!
+ }
+
+ """Information about pagination in a connection."""
+ type PageInfo {
+ """When paginating forwards, are there more items?"""
+ hasNextPage: Boolean!
+
+ """When paginating backwards, are there more items?"""
+ hasPreviousPage: Boolean!
+
+ """When paginating backwards, the cursor to continue."""
+ startCursor: String
+
+ """When paginating forwards, the cursor to continue."""
+ endCursor: String
+ }
+
+ """A connection to a list of items."""
+ type PeopleConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [PeopleEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ people: [Person]
+ }
+
+ """An edge in a connection."""
+ type PeopleEdge {
+ """The item at the end of the edge"""
+ node: Person
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """An individual person or character within the Star Wars universe."""
+ type Person implements Node {
+ """The name of this person."""
+ name: String
+
+ """
+ The birth year of the person, using the in-universe standard of BBY or ABY -
+ Before the Battle of Yavin or After the Battle of Yavin. The Battle of Yavin is
+ a battle that occurs at the end of Star Wars episode IV: A New Hope.
+ """
+ birthYear: String
+
+ """
+ The eye color of this person. Will be "unknown" if not known or "n/a" if the
+ person does not have an eye.
+ """
+ eyeColor: String
+
+ """
+ The gender of this person. Either "Male", "Female" or "unknown",
+ "n/a" if the person does not have a gender.
+ """
+ gender: String
+
+ """
+ The hair color of this person. Will be "unknown" if not known or "n/a" if the
+ person does not have hair.
+ """
+ hairColor: String
+
+ """The height of the person in centimeters."""
+ height: Int
+
+ """The mass of the person in kilograms."""
+ mass: Float
+
+ """The skin color of this person."""
+ skinColor: String
+
+ """A planet that this person was born on or inhabits."""
+ homeworld: Planet
+ filmConnection(after: String, first: Int, before: String, last: Int): PersonFilmsConnection
+
+ """The species that this person belongs to, or null if unknown."""
+ species: Species
+ starshipConnection(after: String, first: Int, before: String, last: Int): PersonStarshipsConnection
+ vehicleConnection(after: String, first: Int, before: String, last: Int): PersonVehiclesConnection
+
+ """The ISO 8601 date format of the time that this resource was created."""
+ created: String
+
+ """The ISO 8601 date format of the time that this resource was edited."""
+ edited: String
+
+ """The ID of an object"""
+ id: ID!
+ }
+
+ """A connection to a list of items."""
+ type PersonFilmsConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [PersonFilmsEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ films: [Film]
+ }
+
+ """An edge in a connection."""
+ type PersonFilmsEdge {
+ """The item at the end of the edge"""
+ node: Film
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A connection to a list of items."""
+ type PersonStarshipsConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [PersonStarshipsEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ starships: [Starship]
+ }
+
+ """An edge in a connection."""
+ type PersonStarshipsEdge {
+ """The item at the end of the edge"""
+ node: Starship
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A connection to a list of items."""
+ type PersonVehiclesConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [PersonVehiclesEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ vehicles: [Vehicle]
+ }
+
+ """An edge in a connection."""
+ type PersonVehiclesEdge {
+ """The item at the end of the edge"""
+ node: Vehicle
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """
+ A large mass, planet or planetoid in the Star Wars Universe, at the time of
+ 0 ABY.
+ """
+ type Planet implements Node {
+ """The name of this planet."""
+ name: String
+
+ """The diameter of this planet in kilometers."""
+ diameter: Int
+
+ """
+ The number of standard hours it takes for this planet to complete a single
+ rotation on its axis.
+ """
+ rotationPeriod: Int
+
+ """
+ The number of standard days it takes for this planet to complete a single orbit
+ of its local star.
+ """
+ orbitalPeriod: Int
+
+ """
+ A number denoting the gravity of this planet, where "1" is normal or 1 standard
+ G. "2" is twice or 2 standard Gs. "0.5" is half or 0.5 standard Gs.
+ """
+ gravity: String
+
+ """The average population of sentient beings inhabiting this planet."""
+ population: Float
+
+ """The climates of this planet."""
+ climates: [String]
+
+ """The terrains of this planet."""
+ terrains: [String]
+
+ """
+ The percentage of the planet surface that is naturally occuring water or bodies
+ of water.
+ """
+ surfaceWater: Float
+ residentConnection(after: String, first: Int, before: String, last: Int): PlanetResidentsConnection
+ filmConnection(after: String, first: Int, before: String, last: Int): PlanetFilmsConnection
+
+ """The ISO 8601 date format of the time that this resource was created."""
+ created: String
+
+ """The ISO 8601 date format of the time that this resource was edited."""
+ edited: String
+
+ """The ID of an object"""
+ id: ID!
+ }
+
+ """A connection to a list of items."""
+ type PlanetFilmsConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [PlanetFilmsEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ films: [Film]
+ }
+
+ """An edge in a connection."""
+ type PlanetFilmsEdge {
+ """The item at the end of the edge"""
+ node: Film
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A connection to a list of items."""
+ type PlanetResidentsConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [PlanetResidentsEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ residents: [Person]
+ }
+
+ """An edge in a connection."""
+ type PlanetResidentsEdge {
+ """The item at the end of the edge"""
+ node: Person
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A connection to a list of items."""
+ type PlanetsConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [PlanetsEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ planets: [Planet]
+ }
+
+ """An edge in a connection."""
+ type PlanetsEdge {
+ """The item at the end of the edge"""
+ node: Planet
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ type Root {
+ allFilms(after: String, first: Int, before: String, last: Int): FilmsConnection
+ film(id: ID, filmID: ID): Film
+ allPeople(after: String, first: Int, before: String, last: Int): PeopleConnection
+ person(id: ID, personID: ID): Person
+ allPlanets(after: String, first: Int, before: String, last: Int): PlanetsConnection
+ planet(id: ID, planetID: ID): Planet
+ allSpecies(after: String, first: Int, before: String, last: Int): SpeciesConnection
+ species(id: ID, speciesID: ID): Species
+ allStarships(after: String, first: Int, before: String, last: Int): StarshipsConnection
+ starship(id: ID, starshipID: ID): Starship
+ allVehicles(after: String, first: Int, before: String, last: Int): VehiclesConnection
+ vehicle(id: ID, vehicleID: ID): Vehicle
+
+ """Fetches an object given its ID"""
+ node(
+ """The ID of an object"""
+ id: ID!
+ ): Node
+ }
+
+ """A type of person or character within the Star Wars Universe."""
+ type Species implements Node {
+ """The name of this species."""
+ name: String
+
+ """The classification of this species, such as "mammal" or "reptile"."""
+ classification: String
+
+ """The designation of this species, such as "sentient"."""
+ designation: String
+
+ """The average height of this species in centimeters."""
+ averageHeight: Float
+
+ """The average lifespan of this species in years, null if unknown."""
+ averageLifespan: Int
+
+ """
+ Common eye colors for this species, null if this species does not typically
+ have eyes.
+ """
+ eyeColors: [String]
+
+ """
+ Common hair colors for this species, null if this species does not typically
+ have hair.
+ """
+ hairColors: [String]
+
+ """
+ Common skin colors for this species, null if this species does not typically
+ have skin.
+ """
+ skinColors: [String]
+
+ """The language commonly spoken by this species."""
+ language: String
+
+ """A planet that this species originates from."""
+ homeworld: Planet
+ personConnection(after: String, first: Int, before: String, last: Int): SpeciesPeopleConnection
+ filmConnection(after: String, first: Int, before: String, last: Int): SpeciesFilmsConnection
+
+ """The ISO 8601 date format of the time that this resource was created."""
+ created: String
+
+ """The ISO 8601 date format of the time that this resource was edited."""
+ edited: String
+
+ """The ID of an object"""
+ id: ID!
+ }
+
+ """A connection to a list of items."""
+ type SpeciesConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [SpeciesEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ species: [Species]
+ }
+
+ """An edge in a connection."""
+ type SpeciesEdge {
+ """The item at the end of the edge"""
+ node: Species
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A connection to a list of items."""
+ type SpeciesFilmsConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [SpeciesFilmsEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ films: [Film]
+ }
+
+ """An edge in a connection."""
+ type SpeciesFilmsEdge {
+ """The item at the end of the edge"""
+ node: Film
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A connection to a list of items."""
+ type SpeciesPeopleConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [SpeciesPeopleEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ people: [Person]
+ }
+
+ """An edge in a connection."""
+ type SpeciesPeopleEdge {
+ """The item at the end of the edge"""
+ node: Person
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A single transport craft that has hyperdrive capability."""
+ type Starship implements Node {
+ """The name of this starship. The common name, such as "Death Star"."""
+ name: String
+
+ """
+ The model or official name of this starship. Such as "T-65 X-wing" or "DS-1
+ Orbital Battle Station".
+ """
+ model: String
+
+ """
+ The class of this starship, such as "Starfighter" or "Deep Space Mobile
+ Battlestation"
+ """
+ starshipClass: String
+
+ """The manufacturers of this starship."""
+ manufacturers: [String]
+
+ """The cost of this starship new, in galactic credits."""
+ costInCredits: Float
+
+ """The length of this starship in meters."""
+ length: Float
+
+ """The number of personnel needed to run or pilot this starship."""
+ crew: String
+
+ """The number of non-essential people this starship can transport."""
+ passengers: String
+
+ """
+ The maximum speed of this starship in atmosphere. null if this starship is
+ incapable of atmosphering flight.
+ """
+ maxAtmospheringSpeed: Int
+
+ """The class of this starships hyperdrive."""
+ hyperdriveRating: Float
+
+ """
+ The Maximum number of Megalights this starship can travel in a standard hour.
+ A "Megalight" is a standard unit of distance and has never been defined before
+ within the Star Wars universe. This figure is only really useful for measuring
+ the difference in speed of starships. We can assume it is similar to AU, the
+ distance between our Sun (Sol) and Earth.
+ """
+ MGLT: Int
+
+ """The maximum number of kilograms that this starship can transport."""
+ cargoCapacity: Float
+
+ """
+ The maximum length of time that this starship can provide consumables for its
+ entire crew without having to resupply.
+ """
+ consumables: String
+ pilotConnection(after: String, first: Int, before: String, last: Int): StarshipPilotsConnection
+ filmConnection(after: String, first: Int, before: String, last: Int): StarshipFilmsConnection
+
+ """The ISO 8601 date format of the time that this resource was created."""
+ created: String
+
+ """The ISO 8601 date format of the time that this resource was edited."""
+ edited: String
+
+ """The ID of an object"""
+ id: ID!
+ }
+
+ """A connection to a list of items."""
+ type StarshipFilmsConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [StarshipFilmsEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ films: [Film]
+ }
+
+ """An edge in a connection."""
+ type StarshipFilmsEdge {
+ """The item at the end of the edge"""
+ node: Film
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A connection to a list of items."""
+ type StarshipPilotsConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [StarshipPilotsEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ pilots: [Person]
+ }
+
+ """An edge in a connection."""
+ type StarshipPilotsEdge {
+ """The item at the end of the edge"""
+ node: Person
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A connection to a list of items."""
+ type StarshipsConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [StarshipsEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ starships: [Starship]
+ }
+
+ """An edge in a connection."""
+ type StarshipsEdge {
+ """The item at the end of the edge"""
+ node: Starship
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A single transport craft that does not have hyperdrive capability"""
+ type Vehicle implements Node {
+ """
+ The name of this vehicle. The common name, such as "Sand Crawler" or "Speeder
+ bike".
+ """
+ name: String
+
+ """
+ The model or official name of this vehicle. Such as "All-Terrain Attack
+ Transport".
+ """
+ model: String
+
+ """The class of this vehicle, such as "Wheeled" or "Repulsorcraft"."""
+ vehicleClass: String
+
+ """The manufacturers of this vehicle."""
+ manufacturers: [String]
+
+ """The cost of this vehicle new, in Galactic Credits."""
+ costInCredits: Float
+
+ """The length of this vehicle in meters."""
+ length: Float
+
+ """The number of personnel needed to run or pilot this vehicle."""
+ crew: String
+
+ """The number of non-essential people this vehicle can transport."""
+ passengers: String
+
+ """The maximum speed of this vehicle in atmosphere."""
+ maxAtmospheringSpeed: Int
+
+ """The maximum number of kilograms that this vehicle can transport."""
+ cargoCapacity: Float
+
+ """
+ The maximum length of time that this vehicle can provide consumables for its
+ entire crew without having to resupply.
+ """
+ consumables: String
+ pilotConnection(after: String, first: Int, before: String, last: Int): VehiclePilotsConnection
+ filmConnection(after: String, first: Int, before: String, last: Int): VehicleFilmsConnection
+
+ """The ISO 8601 date format of the time that this resource was created."""
+ created: String
+
+ """The ISO 8601 date format of the time that this resource was edited."""
+ edited: String
+
+ """The ID of an object"""
+ id: ID!
+ }
+
+ """A connection to a list of items."""
+ type VehicleFilmsConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [VehicleFilmsEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ films: [Film]
+ }
+
+ """An edge in a connection."""
+ type VehicleFilmsEdge {
+ """The item at the end of the edge"""
+ node: Film
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A connection to a list of items."""
+ type VehiclePilotsConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [VehiclePilotsEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ pilots: [Person]
+ }
+
+ """An edge in a connection."""
+ type VehiclePilotsEdge {
+ """The item at the end of the edge"""
+ node: Person
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
+
+ """A connection to a list of items."""
+ type VehiclesConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [VehiclesEdge]
+
+ """
+ A count of the total number of objects in this connection, ignoring pagination.
+ This allows a client to fetch the first five objects by passing "5" as the
+ argument to "first", then fetch the total count so it could display "5 of 83",
+ for example.
+ """
+ totalCount: Int
+
+ """
+ A list of all of the objects returned in the connection. This is a convenience
+ field provided for quickly exploring the API; rather than querying for
+ "{ edges { node } }" when no edge data is needed, this field can be be used
+ instead. Note that when clients like Relay need to fetch the "cursor" field on
+ the edge to enable efficient pagination, this shortcut cannot be used, and the
+ full "{ edges { node } }" version should be used instead.
+ """
+ vehicles: [Vehicle]
+ }
+
+ """An edge in a connection."""
+ type VehiclesEdge {
+ """The item at the end of the edge"""
+ node: Vehicle
+
+ """A cursor for use in pagination"""
+ cursor: String!
+ }
diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json
index 0d18125d43..96433a0f0e 100644
--- a/packages/catalog-model/package.json
+++ b/packages/catalog-model/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-model",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,7 +20,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/config": "^0.1.1-alpha.21",
+ "@backstage/config": "^0.1.1-alpha.23",
"@types/json-schema": "^7.0.5",
"@types/yup": "^0.28.2",
"json-schema": "^0.2.5",
@@ -29,7 +29,7 @@
"yup": "^0.29.1"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.21",
+ "@backstage/cli": "^0.1.1-alpha.23",
"@types/express": "^4.17.6",
"@types/jest": "^26.0.7",
"@types/lodash": "^4.14.151",
diff --git a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.test.ts
index 48de569213..35a1c59ef8 100644
--- a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.test.ts
+++ b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.test.ts
@@ -33,6 +33,8 @@ describe('ApiV1alpha1Policy', () => {
},
spec: {
type: 'openapi',
+ lifecycle: 'production',
+ owner: 'me',
definition: `
openapi: "3.0.0"
info:
@@ -109,6 +111,36 @@ components:
await expect(policy.enforce(entity)).rejects.toThrow(/type/);
});
+ it('rejects missing lifecycle', async () => {
+ delete (entity as any).spec.lifecycle;
+ await expect(policy.enforce(entity)).rejects.toThrow(/lifecycle/);
+ });
+
+ it('rejects wrong lifecycle', async () => {
+ (entity as any).spec.lifecycle = 7;
+ await expect(policy.enforce(entity)).rejects.toThrow(/lifecycle/);
+ });
+
+ it('rejects empty lifecycle', async () => {
+ (entity as any).spec.lifecycle = '';
+ await expect(policy.enforce(entity)).rejects.toThrow(/lifecycle/);
+ });
+
+ it('rejects missing owner', async () => {
+ delete (entity as any).spec.owner;
+ await expect(policy.enforce(entity)).rejects.toThrow(/owner/);
+ });
+
+ it('rejects wrong owner', async () => {
+ (entity as any).spec.owner = 7;
+ await expect(policy.enforce(entity)).rejects.toThrow(/owner/);
+ });
+
+ it('rejects empty owner', async () => {
+ (entity as any).spec.owner = '';
+ await expect(policy.enforce(entity)).rejects.toThrow(/owner/);
+ });
+
it('rejects missing definition', async () => {
delete (entity as any).spec.definition;
await expect(policy.enforce(entity)).rejects.toThrow(/definition/);
diff --git a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts
index 59e16df9a7..972f6df96d 100644
--- a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts
+++ b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts
@@ -26,6 +26,8 @@ export interface ApiEntityV1alpha1 extends Entity {
kind: typeof KIND;
spec: {
type: string;
+ lifecycle: string;
+ owner: string;
definition: string;
};
}
@@ -40,6 +42,8 @@ export class ApiEntityV1alpha1Policy implements EntityPolicy {
spec: yup
.object({
type: yup.string().required().min(1),
+ lifecycle: yup.string().required().min(1),
+ owner: yup.string().required().min(1),
definition: yup.string().required().min(1),
})
.required(),
diff --git a/packages/cli-common/package.json b/packages/cli-common/package.json
index 4132907e70..bbd9a67387 100644
--- a/packages/cli-common/package.json
+++ b/packages/cli-common/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/cli-common",
"description": "Common functionality used by cli, backend, and create-app",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"private": false,
"main": "src/index.ts",
"types": "src/index.ts",
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 492cd3b958..6f65658539 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/cli",
"description": "CLI for developing Backstage plugins and apps",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public"
@@ -28,9 +28,9 @@
"backstage-cli": "bin/backstage-cli"
},
"dependencies": {
- "@backstage/cli-common": "^0.1.1-alpha.21",
- "@backstage/config": "^0.1.1-alpha.21",
- "@backstage/config-loader": "^0.1.1-alpha.21",
+ "@backstage/cli-common": "^0.1.1-alpha.23",
+ "@backstage/config": "^0.1.1-alpha.23",
+ "@backstage/config-loader": "^0.1.1-alpha.23",
"@hot-loader/react-dom": "^16.13.0",
"@lerna/package-graph": "^3.18.5",
"@lerna/project": "^3.18.0",
diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts
index 5e6882a4a0..247812b6fd 100644
--- a/packages/cli/src/commands/create-plugin/createPlugin.ts
+++ b/packages/cli/src/commands/create-plugin/createPlugin.ts
@@ -21,6 +21,7 @@ import inquirer, { Answers, Question } from 'inquirer';
import { exec as execCb } from 'child_process';
import { resolve as resolvePath } from 'path';
import os from 'os';
+import { Command } from 'commander';
import {
parseOwnerIds,
addCodeownersEntry,
@@ -32,12 +33,12 @@ import { version as backstageVersion } from '../../lib/version';
const exec = promisify(execCb);
-async function checkExists(rootDir: string, id: string) {
- await Task.forItem('checking', id, async () => {
- const destination = resolvePath(rootDir, 'plugins', id);
-
+async function checkExists(destination: string) {
+ await Task.forItem('checking', destination, async () => {
if (await fs.pathExists(destination)) {
- const existing = chalk.cyan(destination.replace(`${rootDir}/`, ''));
+ const existing = chalk.cyan(
+ destination.replace(`${paths.targetRoot}/`, ''),
+ );
throw new Error(
`A plugin with the same name already exists: ${existing}\nPlease try again with a different plugin ID`,
);
@@ -86,10 +87,9 @@ export const addExportStatement = async (
export async function addPluginDependencyToApp(
rootDir: string,
- pluginName: string,
+ pluginPackage: string,
versionStr: string,
) {
- const pluginPackage = `@backstage/plugin-${pluginName}`;
const packageFilePath = 'packages/app/package.json';
const packageFile = resolvePath(rootDir, packageFilePath);
@@ -116,8 +116,11 @@ export async function addPluginDependencyToApp(
});
}
-export async function addPluginToApp(rootDir: string, pluginName: string) {
- const pluginPackage = `@backstage/plugin-${pluginName}`;
+export async function addPluginToApp(
+ rootDir: string,
+ pluginName: string,
+ pluginPackage: string,
+) {
const pluginNameCapitalized = pluginName
.split('-')
.map(name => capitalize(name))
@@ -175,7 +178,7 @@ export async function movePlugin(
});
}
-export default async () => {
+export default async (cmd: Command) => {
const codeownersPath = await getCodeownersFilePath(paths.targetRoot);
const questions: Question[] = [
@@ -221,20 +224,29 @@ export default async () => {
}
const answers: Answers = await inquirer.prompt(questions);
-
+ const name = cmd.scope
+ ? `@${cmd.scope.replace(/^@/, '')}/plugin-${answers.id}`
+ : `plugin-${answers.id}`;
+ const npmRegistry = cmd.npmRegistry && cmd.scope ? cmd.npmRegistry : '';
+ const privatePackage = cmd.private === false ? false : true;
+ const isMonoRepo = await fs.pathExists(paths.resolveTargetRoot('lerna.json'));
const appPackage = paths.resolveTargetRoot('packages/app');
const templateDir = paths.resolveOwn('templates/default-plugin');
const tempDir = resolvePath(os.tmpdir(), answers.id);
- const pluginDir = paths.resolveTargetRoot('plugins', answers.id);
+ const pluginDir = isMonoRepo
+ ? paths.resolveTargetRoot('plugins', answers.id)
+ : paths.resolveTargetRoot(answers.id);
const ownerIds = parseOwnerIds(answers.owner);
- const { version } = await fs.readJson(paths.resolveTargetRoot('lerna.json'));
+ const { version } = isMonoRepo
+ ? await fs.readJson(paths.resolveTargetRoot('lerna.json'))
+ : { version: '0.1.0' };
Task.log();
Task.log('Creating the plugin...');
try {
Task.section('Checking if the plugin ID is available');
- await checkExists(paths.targetRoot, answers.id);
+ await checkExists(pluginDir);
Task.section('Creating a temporary plugin directory');
await createTemporaryPluginFolder(tempDir);
@@ -244,6 +256,9 @@ export default async () => {
...answers,
version,
backstageVersion,
+ name,
+ privatePackage,
+ npmRegistry,
});
Task.section('Moving to final location');
@@ -254,10 +269,10 @@ export default async () => {
if (await fs.pathExists(appPackage)) {
Task.section('Adding plugin as dependency in app');
- await addPluginDependencyToApp(paths.targetRoot, answers.id, version);
+ await addPluginDependencyToApp(paths.targetRoot, name, version);
Task.section('Import plugin in app');
- await addPluginToApp(paths.targetRoot, answers.id);
+ await addPluginToApp(paths.targetRoot, answers.id, name);
}
if (ownerIds && ownerIds.length) {
@@ -269,11 +284,7 @@ export default async () => {
}
Task.log();
- Task.log(
- `🥇 Successfully created ${chalk.cyan(
- `@backstage/plugin-${answers.id}`,
- )}`,
- );
+ Task.log(`🥇 Successfully created ${chalk.cyan(`${name}`)}`);
Task.log();
Task.exit();
} catch (error) {
diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts
index 4e031cdbc1..f1828cef84 100644
--- a/packages/cli/src/commands/index.ts
+++ b/packages/cli/src/commands/index.ts
@@ -62,6 +62,9 @@ export function registerCommands(program: CommanderStatic) {
program
.command('create-plugin')
.description('Creates a new plugin in the current repository')
+ .option('--scope ', 'NPM scope')
+ .option('--npm-registry ', 'NPM registry URL')
+ .option('--no-private', 'Public NPM Package')
.action(
lazy(() => import('./create-plugin/createPlugin').then(m => m.default)),
);
diff --git a/packages/cli/src/commands/plugin/diff.ts b/packages/cli/src/commands/plugin/diff.ts
index 99af093f2c..f5dfa670b7 100644
--- a/packages/cli/src/commands/plugin/diff.ts
+++ b/packages/cli/src/commands/plugin/diff.ts
@@ -30,6 +30,9 @@ import { version as backstageVersion } from '../../lib/version';
export type PluginData = {
id: string;
name: string;
+ privatePackage: string;
+ version: string;
+ npmRegistry: string;
};
const fileHandlers = [
@@ -62,11 +65,8 @@ export default async (cmd: Command) => {
promptFunc = yesPromptFunc;
}
- const { version } = await fs.readJson(paths.resolveTargetRoot('lerna.json'));
-
const data = await readPluginData();
const templateFiles = await diffTemplateFiles('default-plugin', {
- version,
backstageVersion,
...data,
});
@@ -77,9 +77,19 @@ export default async (cmd: Command) => {
// Reads templating data from the existing plugin
async function readPluginData(): Promise {
let name: string;
+ let privatePackage: string;
+ let version: string;
+ let npmRegistry: string;
try {
const pkg = require(paths.resolveTarget('package.json'));
name = pkg.name;
+ privatePackage = pkg.private;
+ version = pkg.version;
+ const scope = name.split('/')[0];
+ if (`${scope}:registry` in pkg.publishConfig) {
+ const registryURL = pkg.publishConfig[`${scope}:registry`];
+ npmRegistry = `"${scope}:registry" : "${registryURL}"`;
+ } else npmRegistry = '';
} catch (error) {
throw new Error(`Failed to read target package, ${error}`);
}
@@ -96,5 +106,5 @@ async function readPluginData(): Promise {
const id = pluginIdMatch[1];
- return { id, name };
+ return { id, name, privatePackage, version, npmRegistry };
}
diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts
index e91fc32633..afcedaae59 100644
--- a/packages/cli/src/lib/builder/config.ts
+++ b/packages/cli/src/lib/builder/config.ts
@@ -94,14 +94,7 @@ export const makeConfigs = async (
postcss(),
imageFiles({
exclude: /\.icon\.svg$/,
- include: [
- /\.css$/,
- /\.svg$/,
- /\.png$/,
- /\.gif$/,
- /\.jpg$/,
- /\.jpeg$/,
- ],
+ include: [/\.svg$/, /\.png$/, /\.gif$/, /\.jpg$/, /\.jpeg$/],
}),
json(),
yaml(),
diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs
index dbfb639b16..52cffeb621 100644
--- a/packages/cli/templates/default-plugin/package.json.hbs
+++ b/packages/cli/templates/default-plugin/package.json.hbs
@@ -1,11 +1,14 @@
{
- "name": "@backstage/plugin-{{id}}",
+ "name": "{{name}}",
"version": "{{version}}",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
- "private": true,
+{{#if privatePackage}} "private": {{privatePackage}},
+{{/if}}
"publishConfig": {
+{{#if npmRegistry}} "registry": "{{npmRegistry}}",
+{{/if}}
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json
index 592b8eefb3..09f8da3b70 100644
--- a/packages/config-loader/package.json
+++ b/packages/config-loader/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/config-loader",
"description": "Config loading functionality used by Backstage backend, and CLI",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
@@ -30,7 +30,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/config": "^0.1.1-alpha.21",
+ "@backstage/config": "^0.1.1-alpha.23",
"fs-extra": "^9.0.0",
"yaml": "^1.9.2",
"yup": "^0.29.1"
diff --git a/packages/config/package.json b/packages/config/package.json
index 236964f2bd..58c86d6e96 100644
--- a/packages/config/package.json
+++ b/packages/config/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/config",
"description": "Config API used by Backstage core, backend, and CLI",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
diff --git a/packages/core-api/package.json b/packages/core-api/package.json
index 191394e536..7d338c944d 100644
--- a/packages/core-api/package.json
+++ b/packages/core-api/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-api",
"description": "Internal Core API used by Backstage plugins and apps",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
@@ -29,8 +29,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/config": "^0.1.1-alpha.21",
- "@backstage/theme": "^0.1.1-alpha.21",
+ "@backstage/config": "^0.1.1-alpha.23",
+ "@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@types/react": "^16.9",
@@ -41,8 +41,8 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.21",
- "@backstage/test-utils-core": "^0.1.1-alpha.21",
+ "@backstage/cli": "^0.1.1-alpha.23",
+ "@backstage/test-utils-core": "^0.1.1-alpha.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
diff --git a/packages/core-api/src/apis/definitions/IdentityApi.ts b/packages/core-api/src/apis/definitions/IdentityApi.ts
index 13f4cd24bc..2684422b1e 100644
--- a/packages/core-api/src/apis/definitions/IdentityApi.ts
+++ b/packages/core-api/src/apis/definitions/IdentityApi.ts
@@ -46,9 +46,9 @@ export type IdentityApi = {
// TODO: getProfile(): Promise - We want this to be async when added, but needs more work.
/**
- * Log out the current user
+ * Sign out the current user
*/
- logout(): Promise;
+ signOut(): Promise;
};
export const identityApiRef = createApiRef({
diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts
index 1ff8c46dad..800a865fea 100644
--- a/packages/core-api/src/apis/definitions/auth.ts
+++ b/packages/core-api/src/apis/definitions/auth.ts
@@ -90,11 +90,6 @@ export type OAuthApi = {
scope?: OAuthScope,
options?: AuthRequestOptions,
): Promise;
-
- /**
- * Log out the user's session. This will reload the page.
- */
- logout(): Promise;
};
/**
@@ -114,11 +109,6 @@ export type OpenIdConnectApi = {
* The returned promise can be rejected, but only if the user rejects the login request.
*/
getIdToken(options?: AuthRequestOptions): Promise;
-
- /**
- * Log out the user's session. This will reload the page.
- */
- logout(): Promise;
};
/**
@@ -187,7 +177,7 @@ export type ProfileInfo = {
};
/**
- * Session state values passed to subscribers of the SessionStateApi.
+ * Session state values passed to subscribers of the SessionApi.
*/
export enum SessionState {
SignedIn = 'SignedIn',
@@ -195,10 +185,22 @@ export enum SessionState {
}
/**
- * This API provides access to an sessionState$ observable which provides an update when the
- * user performs a sign in or sign out from an auth provider.
+ * The SessionApi provides basic controls for any auth provider that is tied to a persistent session.
*/
-export type SessionStateApi = {
+export type SessionApi = {
+ /**
+ * Sign in with a minimum set of permissions.
+ */
+ signIn(): Promise;
+
+ /**
+ * Sign out from the current session. This will reload the page.
+ */
+ signOut(): Promise;
+
+ /**
+ * Observe the current state of the auth session. Emits the current state on subscription.
+ */
sessionState$(): Observable;
};
@@ -215,7 +217,7 @@ export const googleAuthApiRef = createApiRef<
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
- SessionStateApi
+ SessionApi
>({
id: 'core.auth.google',
description: 'Provides authentication towards Google APIs and identities',
@@ -228,7 +230,7 @@ export const googleAuthApiRef = createApiRef<
* for a full list of supported scopes.
*/
export const githubAuthApiRef = createApiRef<
- OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi
+ OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
>({
id: 'core.auth.github',
description: 'Provides authentication towards GitHub APIs',
@@ -245,7 +247,7 @@ export const oktaAuthApiRef = createApiRef<
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
- SessionStateApi
+ SessionApi
>({
id: 'core.auth.okta',
description: 'Provides authentication towards Okta APIs',
@@ -258,7 +260,7 @@ export const oktaAuthApiRef = createApiRef<
* for a full list of supported scopes.
*/
export const gitlabAuthApiRef = createApiRef<
- OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi
+ OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
>({
id: 'core.auth.gitlab',
description: 'Provides authentication towards GitLab APIs',
@@ -271,7 +273,7 @@ export const gitlabAuthApiRef = createApiRef<
* for a full list of supported scopes.
*/
export const auth0AuthApiRef = createApiRef<
- OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi
+ OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
>({
id: 'core.auth.auth0',
description: 'Provides authentication towards Auth0 APIs',
@@ -289,7 +291,7 @@ export const microsoftAuthApiRef = createApiRef<
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
- SessionStateApi
+ SessionApi
>({
id: 'core.auth.microsoft',
description: 'Provides authentication towards Microsoft APIs and identities',
@@ -302,8 +304,8 @@ export const oauth2ApiRef = createApiRef<
OAuthApi &
OpenIdConnectApi &
ProfileInfoApi &
- SessionStateApi &
- BackstageIdentityApi
+ BackstageIdentityApi &
+ SessionApi
>({
id: 'core.auth.oauth2',
description: 'Example of how to use oauth2 custom provider',
diff --git a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts b/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts
index 8b9f807cd8..1d87ff3720 100644
--- a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts
+++ b/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts
@@ -19,7 +19,7 @@ import { DefaultAuthConnector } from '../../../../lib/AuthConnector';
import { GithubSession } from './types';
import {
OAuthApi,
- SessionStateApi,
+ SessionApi,
SessionState,
ProfileInfo,
BackstageIdentity,
@@ -61,7 +61,7 @@ const DEFAULT_PROVIDER = {
icon: GithubIcon,
};
-class GithubAuth implements OAuthApi, SessionStateApi {
+class GithubAuth implements OAuthApi, SessionApi {
static create({
discoveryApi,
environment = 'development',
@@ -102,12 +102,20 @@ class GithubAuth implements OAuthApi, SessionStateApi {
return new GithubAuth(authSessionStore);
}
+ constructor(private readonly sessionManager: SessionManager) {}
+
+ async signIn() {
+ await this.getAccessToken();
+ }
+
+ async signOut() {
+ await this.sessionManager.removeSession();
+ }
+
sessionState$(): Observable {
return this.sessionManager.sessionState$();
}
- constructor(private readonly sessionManager: SessionManager) {}
-
async getAccessToken(scope?: string, options?: AuthRequestOptions) {
const session = await this.sessionManager.getSession({
...options,
@@ -128,10 +136,6 @@ class GithubAuth implements OAuthApi, SessionStateApi {
return session?.profile;
}
- async logout() {
- await this.sessionManager.removeSession();
- }
-
static normalizeScope(scope?: string): Set {
if (!scope) {
return new Set();
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 27626aac5a..d088ca9798 100644
--- a/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.ts
+++ b/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.ts
@@ -32,7 +32,7 @@ import {
ProfileInfo,
ProfileInfoApi,
SessionState,
- SessionStateApi,
+ SessionApi,
BackstageIdentityApi,
} from '../../../definitions/auth';
import { OAuth2Session } from './types';
@@ -75,7 +75,7 @@ class OAuth2
OpenIdConnectApi,
ProfileInfoApi,
BackstageIdentityApi,
- SessionStateApi {
+ SessionApi {
static create({
discoveryApi,
environment = 'development',
@@ -129,6 +129,14 @@ class OAuth2
this.scopeTransform = options.scopeTransform;
}
+ async signIn() {
+ await this.getAccessToken();
+ }
+
+ async signOut() {
+ await this.sessionManager.removeSession();
+ }
+
sessionState$(): Observable {
return this.sessionManager.sessionState$();
}
@@ -150,10 +158,6 @@ class OAuth2
return session?.providerInfo.idToken ?? '';
}
- async logout() {
- await this.sessionManager.removeSession();
- }
-
async getBackstageIdentity(
options: AuthRequestOptions = {},
): Promise {
diff --git a/packages/core-api/src/app/AppIdentity.ts b/packages/core-api/src/app/AppIdentity.ts
index 69ee5d28ac..d3e5fe567a 100644
--- a/packages/core-api/src/app/AppIdentity.ts
+++ b/packages/core-api/src/app/AppIdentity.ts
@@ -26,7 +26,7 @@ export class AppIdentity implements IdentityApi {
private userId?: string;
private profile?: ProfileInfo;
private idTokenFunc?: () => Promise;
- private logoutFunc?: () => Promise;
+ private signOutFunc?: () => Promise;
getUserId(): string {
if (!this.hasIdentity) {
@@ -55,13 +55,13 @@ export class AppIdentity implements IdentityApi {
return this.idTokenFunc?.();
}
- async logout(): Promise {
+ async signOut(): Promise {
if (!this.hasIdentity) {
throw new Error(
- 'Tried to access IdentityApi logoutFunc before app was loaded',
+ 'Tried to access IdentityApi signOutFunc before app was loaded',
);
}
- await this.logoutFunc?.();
+ await this.signOutFunc?.();
location.reload();
}
@@ -80,6 +80,6 @@ export class AppIdentity implements IdentityApi {
this.userId = result.userId;
this.profile = result.profile;
this.idTokenFunc = result.getIdToken;
- this.logoutFunc = result.logout;
+ this.signOutFunc = result.signOut;
}
}
diff --git a/packages/core-api/src/app/FeatureFlags.tsx b/packages/core-api/src/app/FeatureFlags.tsx
index 11c084d3ed..3db2a18a02 100644
--- a/packages/core-api/src/app/FeatureFlags.tsx
+++ b/packages/core-api/src/app/FeatureFlags.tsx
@@ -80,6 +80,15 @@ export class UserFlags extends Map {
return output;
}
+ toggle(name: FeatureFlagName): FeatureFlagState {
+ if (super.get(name) === FeatureFlagState.On) {
+ super.set(name, FeatureFlagState.Off);
+ } else {
+ super.set(name, FeatureFlagState.On);
+ }
+ return super.get(name) || FeatureFlagState.Off;
+ }
+
delete(name: FeatureFlagName): boolean {
const output = super.delete(name);
this.save();
diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts
index 565e073aed..882ecd3d8b 100644
--- a/packages/core-api/src/app/types.ts
+++ b/packages/core-api/src/app/types.ts
@@ -38,10 +38,11 @@ export type SignInResult = {
* Function used to retrieve an ID token for the signed in user.
*/
getIdToken?: () => Promise;
+
/**
- * Logout handler that will be called if the user requests a logout.
+ * Sign out handler that will be called if the user requests to sign out.
*/
- logout?: () => Promise;
+ signOut?: () => Promise;
};
export type SignInPageProps = {
diff --git a/packages/core/package.json b/packages/core/package.json
index 7e9932bdc1..8749a37b92 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/core",
"description": "Core API used by Backstage plugins and apps",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
@@ -29,9 +29,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/config": "^0.1.1-alpha.21",
- "@backstage/core-api": "^0.1.1-alpha.21",
- "@backstage/theme": "^0.1.1-alpha.21",
+ "@backstage/config": "^0.1.1-alpha.23",
+ "@backstage/core-api": "^0.1.1-alpha.23",
+ "@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -54,8 +54,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.21",
- "@backstage/test-utils": "^0.1.1-alpha.21",
+ "@backstage/cli": "^0.1.1-alpha.23",
+ "@backstage/test-utils": "^0.1.1-alpha.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
diff --git a/packages/core/src/components/CodeSnippet/CodeSnippet.stories.tsx b/packages/core/src/components/CodeSnippet/CodeSnippet.stories.tsx
index c0f57d6330..77f3ca51cc 100644
--- a/packages/core/src/components/CodeSnippet/CodeSnippet.stories.tsx
+++ b/packages/core/src/components/CodeSnippet/CodeSnippet.stories.tsx
@@ -86,3 +86,9 @@ export const Languages = () => (
);
+
+export const CopyCode = () => (
+
+
+
+);
diff --git a/packages/core/src/components/CodeSnippet/CodeSnippet.test.tsx b/packages/core/src/components/CodeSnippet/CodeSnippet.test.tsx
index 42efbf14af..1a76f84f44 100644
--- a/packages/core/src/components/CodeSnippet/CodeSnippet.test.tsx
+++ b/packages/core/src/components/CodeSnippet/CodeSnippet.test.tsx
@@ -15,7 +15,7 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
+import { render, fireEvent } from '@testing-library/react';
import { wrapInTestApp } from '@backstage/test-utils';
import { CodeSnippet } from './CodeSnippet';
@@ -55,4 +55,14 @@ describe(' ', () => {
expect(queryByText(/2/)).toBeInTheDocument();
expect(queryByText(/3/)).toBeInTheDocument();
});
+
+ it('copy code using button', async () => {
+ document.execCommand = jest.fn();
+ const rendered = render(
+ wrapInTestApp( ),
+ );
+ const button = rendered.getByTitle('Text copied to clipboard');
+ fireEvent.click(button);
+ expect(document.execCommand).toHaveBeenCalled();
+ });
});
diff --git a/packages/core/src/components/CodeSnippet/CodeSnippet.tsx b/packages/core/src/components/CodeSnippet/CodeSnippet.tsx
index 324d71e936..b6d9b3a5aa 100644
--- a/packages/core/src/components/CodeSnippet/CodeSnippet.tsx
+++ b/packages/core/src/components/CodeSnippet/CodeSnippet.tsx
@@ -20,19 +20,22 @@ import SyntaxHighlighter from 'react-syntax-highlighter';
import { docco, dark } from 'react-syntax-highlighter/dist/cjs/styles/hljs';
import { useTheme } from '@material-ui/core';
import { BackstageTheme } from '@backstage/theme';
+import { CopyTextButton } from '../CopyTextButton';
type Props = {
text: string;
language: string;
showLineNumbers?: boolean;
+ showCopyCodeButton?: boolean;
};
const defaultProps = {
showLineNumbers: false,
+ showCopyCodeButton: false,
};
export const CodeSnippet: FC = props => {
- const { text, language, showLineNumbers } = {
+ const { text, language, showLineNumbers, showCopyCodeButton } = {
...defaultProps,
...props,
};
@@ -41,13 +44,20 @@ export const CodeSnippet: FC = props => {
const mode = theme.palette.type === 'dark' ? dark : docco;
return (
-
- {text}
-
+
+
+ {text}
+
+ {showCopyCodeButton && (
+
+
+
+ )}
+
);
};
@@ -56,4 +66,5 @@ CodeSnippet.propTypes = {
text: PropTypes.string.isRequired,
language: PropTypes.string.isRequired,
showLineNumbers: PropTypes.bool,
+ showCopyCodeButton: PropTypes.bool,
};
diff --git a/packages/core/src/components/CopyTextButton/CopyTextButton.test.tsx b/packages/core/src/components/CopyTextButton/CopyTextButton.test.tsx
index e789035b44..cab992e11a 100644
--- a/packages/core/src/components/CopyTextButton/CopyTextButton.test.tsx
+++ b/packages/core/src/components/CopyTextButton/CopyTextButton.test.tsx
@@ -15,7 +15,7 @@
*/
import React from 'react';
-import { render } from '@testing-library/react';
+import { render, fireEvent } from '@testing-library/react';
import { wrapInTestApp } from '@backstage/test-utils';
import { CopyTextButton } from './CopyTextButton';
import {
@@ -76,7 +76,7 @@ describe(' ', () => {
),
);
const button = rendered.getByTitle('mockTooltip');
- button.click();
+ fireEvent.click(button);
expect(document.execCommand).toHaveBeenCalled();
rendered.getByText('mockTooltip');
});
diff --git a/packages/core/src/components/CopyTextButton/CopyTextButton.tsx b/packages/core/src/components/CopyTextButton/CopyTextButton.tsx
index e4773165cc..cb322e5b56 100644
--- a/packages/core/src/components/CopyTextButton/CopyTextButton.tsx
+++ b/packages/core/src/components/CopyTextButton/CopyTextButton.tsx
@@ -63,7 +63,7 @@ export const CopyTextButton: FC = props => {
};
const classes = useStyles(props);
const errorApi = useApi(errorApiRef);
- const inputRef = useRef(null);
+ const inputRef = useRef(null);
const [open, setOpen] = useState(false);
const handleCopyClick: MouseEventHandler = e => {
@@ -82,9 +82,8 @@ export const CopyTextButton: FC = props => {
return (
<>
-
diff --git a/packages/core/src/components/ProgressBars/ProgressCard.stories.tsx b/packages/core/src/components/ProgressBars/GaugeCard.stories.tsx
similarity index 69%
rename from packages/core/src/components/ProgressBars/ProgressCard.stories.tsx
rename to packages/core/src/components/ProgressBars/GaugeCard.stories.tsx
index 62c9f3be6e..922bb63b2f 100644
--- a/packages/core/src/components/ProgressBars/ProgressCard.stories.tsx
+++ b/packages/core/src/components/ProgressBars/GaugeCard.stories.tsx
@@ -15,26 +15,26 @@
*/
import React from 'react';
-import { ProgressCard } from './ProgressCard';
+import { GaugeCard } from './GaugeCard';
import { Grid } from '@material-ui/core';
const linkInfo = { title: 'Go to XYZ Location', link: '#' };
export default {
title: 'Progress Card',
- component: ProgressCard,
+ component: GaugeCard,
};
export const Default = () => (
-
+
-
+
-
+
);
@@ -42,21 +42,17 @@ export const Default = () => (
export const Subhead = () => (
-
+
-
- (
export const LinkInFooter = () => (
-
+
-
+
-
+
);
diff --git a/packages/core/src/components/ProgressBars/ProgressCard.test.jsx b/packages/core/src/components/ProgressBars/GaugeCard.test.jsx
similarity index 75%
rename from packages/core/src/components/ProgressBars/ProgressCard.test.jsx
rename to packages/core/src/components/ProgressBars/GaugeCard.test.jsx
index 8568077a92..27ef0bb188 100644
--- a/packages/core/src/components/ProgressBars/ProgressCard.test.jsx
+++ b/packages/core/src/components/ProgressBars/GaugeCard.test.jsx
@@ -18,32 +18,30 @@ import React from 'react';
import { render } from '@testing-library/react';
import { wrapInTestApp } from '@backstage/test-utils';
-import { ProgressCard } from './ProgressCard';
+import { GaugeCard } from './GaugeCard';
const minProps = { title: 'Tingle upgrade', progress: 0.12 };
-describe(' ', () => {
+describe(' ', () => {
it('renders without exploding', () => {
- const { getByText } = render(wrapInTestApp( ));
+ const { getByText } = render(wrapInTestApp( ));
expect(getByText(/Tingle.*/)).toBeInTheDocument();
});
it('renders progress and title', () => {
- const { getByText } = render(wrapInTestApp( ));
+ const { getByText } = render(wrapInTestApp( ));
expect(getByText(/Tingle.*/)).toBeInTheDocument();
expect(getByText(/12%.*/)).toBeInTheDocument();
});
it('does not render deepLink', () => {
- const { queryByText } = render(
- wrapInTestApp( ),
- );
+ const { queryByText } = render(wrapInTestApp( ));
expect(queryByText('View more')).not.toBeInTheDocument();
});
it('handles invalid numbers', () => {
const badProps = { title: 'Tingle upgrade', progress: 'hejjo' };
- const { getByText } = render(wrapInTestApp( ));
+ const { getByText } = render(wrapInTestApp( ));
expect(getByText(/N\/A.*/)).toBeInTheDocument();
});
});
diff --git a/packages/core/src/components/ProgressBars/ProgressCard.tsx b/packages/core/src/components/ProgressBars/GaugeCard.tsx
similarity index 90%
rename from packages/core/src/components/ProgressBars/ProgressCard.tsx
rename to packages/core/src/components/ProgressBars/GaugeCard.tsx
index eef6312600..fc7055f705 100644
--- a/packages/core/src/components/ProgressBars/ProgressCard.tsx
+++ b/packages/core/src/components/ProgressBars/GaugeCard.tsx
@@ -18,7 +18,7 @@ import React, { FC } from 'react';
import { makeStyles } from '@material-ui/core';
import { InfoCard } from '../../layout/InfoCard';
import { BottomLinkProps } from '../../layout/BottomLink';
-import { CircleProgress } from './CircleProgress';
+import { GaugeProgress } from './GaugeProgress';
type Props = {
title: string;
@@ -36,7 +36,7 @@ const useStyles = makeStyles({
},
});
-export const ProgressCard: FC = props => {
+export const GaugeCard: FC = props => {
const classes = useStyles(props);
const { title, subheader, progress, deepLink, variant } = props;
@@ -48,7 +48,7 @@ export const ProgressCard: FC = props => {
deepLink={deepLink}
variant={variant}
>
-
+
);
diff --git a/packages/core/src/components/ProgressBars/CircleProgress.test.jsx b/packages/core/src/components/ProgressBars/GaugeProgress.test.jsx
similarity index 82%
rename from packages/core/src/components/ProgressBars/CircleProgress.test.jsx
rename to packages/core/src/components/ProgressBars/GaugeProgress.test.jsx
index 9b9eae1bb8..778abdf12c 100644
--- a/packages/core/src/components/ProgressBars/CircleProgress.test.jsx
+++ b/packages/core/src/components/ProgressBars/GaugeProgress.test.jsx
@@ -17,32 +17,32 @@
import React from 'react';
import { render } from '@testing-library/react';
import { wrapInTestApp } from '@backstage/test-utils';
-import { CircleProgress, getProgressColor } from './CircleProgress';
+import { GaugeProgress, getProgressColor } from './GaugeProgress';
-describe(' ', () => {
+describe(' ', () => {
it('renders without exploding', () => {
const { getByText } = render(
- wrapInTestApp( ),
+ wrapInTestApp( ),
);
getByText('10%');
});
it('handles fractional prop', () => {
const { getByText } = render(
- wrapInTestApp( ),
+ wrapInTestApp( ),
);
getByText('10%');
});
it('handles max prop', () => {
const { getByText } = render(
- wrapInTestApp( ),
+ wrapInTestApp( ),
);
getByText('1%');
});
it('handles unit prop', () => {
const { getByText } = render(
- wrapInTestApp( ),
+ wrapInTestApp( ),
);
getByText('10m');
});
diff --git a/packages/core/src/components/ProgressBars/CircleProgress.tsx b/packages/core/src/components/ProgressBars/GaugeProgress.tsx
similarity index 98%
rename from packages/core/src/components/ProgressBars/CircleProgress.tsx
rename to packages/core/src/components/ProgressBars/GaugeProgress.tsx
index 79a451b14e..14776ed431 100644
--- a/packages/core/src/components/ProgressBars/CircleProgress.tsx
+++ b/packages/core/src/components/ProgressBars/GaugeProgress.tsx
@@ -77,7 +77,7 @@ export function getProgressColor(
return palette.status.ok;
}
-export const CircleProgress: FC = props => {
+export const GaugeProgress: FC = props => {
const classes = useStyles(props);
const theme = useTheme();
const { value, fractional, inverse, unit, max } = {
diff --git a/packages/core/src/components/ProgressBars/HorizontalProgress.stories.tsx b/packages/core/src/components/ProgressBars/LinearGauge.stories.tsx
similarity index 79%
rename from packages/core/src/components/ProgressBars/HorizontalProgress.stories.tsx
rename to packages/core/src/components/ProgressBars/LinearGauge.stories.tsx
index 6e8f4ed7fd..c4492986b6 100644
--- a/packages/core/src/components/ProgressBars/HorizontalProgress.stories.tsx
+++ b/packages/core/src/components/ProgressBars/LinearGauge.stories.tsx
@@ -15,29 +15,29 @@
*/
import React from 'react';
-import { HorizontalProgress } from './HorizontalProgress';
+import { LinearGauge } from './LinearGauge';
const containerStyle = { width: 300 };
export default {
- title: 'HorizontalProgress',
- component: HorizontalProgress,
+ title: 'LinearGauge',
+ component: LinearGauge,
};
export const Default = () => (
-
+
);
export const MediumProgress = () => (
-
+
);
export const LowProgress = () => (
-
+
);
diff --git a/packages/core/src/components/ProgressBars/HorizontalProgress.tsx b/packages/core/src/components/ProgressBars/LinearGauge.tsx
similarity index 92%
rename from packages/core/src/components/ProgressBars/HorizontalProgress.tsx
rename to packages/core/src/components/ProgressBars/LinearGauge.tsx
index 72bf1f34c3..73163b345f 100644
--- a/packages/core/src/components/ProgressBars/HorizontalProgress.tsx
+++ b/packages/core/src/components/ProgressBars/LinearGauge.tsx
@@ -19,7 +19,7 @@ import { Tooltip, useTheme } from '@material-ui/core';
// @ts-ignore
import { Line } from 'rc-progress';
import { BackstageTheme } from '@backstage/theme';
-import { getProgressColor } from './CircleProgress';
+import { getProgressColor } from './GaugeProgress';
type Props = {
/**
@@ -28,7 +28,7 @@ type Props = {
value: number;
};
-export const HorizontalProgress: FC = ({ value }) => {
+export const LinearGauge: FC = ({ value }) => {
const theme = useTheme();
if (isNaN(value)) {
return null;
diff --git a/packages/core/src/components/ProgressBars/index.ts b/packages/core/src/components/ProgressBars/index.ts
index c74e283ae6..c7131c8831 100644
--- a/packages/core/src/components/ProgressBars/index.ts
+++ b/packages/core/src/components/ProgressBars/index.ts
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-export { ProgressCard } from './ProgressCard';
-export { CircleProgress } from './CircleProgress';
-export { HorizontalProgress } from './HorizontalProgress';
+export { GaugeCard } from './GaugeCard';
+export { GaugeProgress } from './GaugeProgress';
+export { LinearGauge } from './LinearGauge';
diff --git a/packages/core/src/layout/InfoCard/InfoCard.tsx b/packages/core/src/layout/InfoCard/InfoCard.tsx
index 6914d396b1..09d31735cd 100644
--- a/packages/core/src/layout/InfoCard/InfoCard.tsx
+++ b/packages/core/src/layout/InfoCard/InfoCard.tsx
@@ -37,6 +37,7 @@ const useStyles = makeStyles(theme => ({
},
},
header: {
+ display: 'inline-block',
padding: theme.spacing(2, 2, 2, 2.5),
},
headerTitle: {
@@ -202,7 +203,7 @@ export const InfoCard = ({
}}
title={title}
subheader={subheader}
- style={{ display: 'inline-block', ...headerStyle }}
+ style={{ ...headerStyle }}
{...headerProps}
/>
diff --git a/packages/core/src/layout/Page/Page.stories.tsx b/packages/core/src/layout/Page/Page.stories.tsx
index 0b91875546..8fd779196d 100644
--- a/packages/core/src/layout/Page/Page.stories.tsx
+++ b/packages/core/src/layout/Page/Page.stories.tsx
@@ -30,7 +30,7 @@ import {
Table,
StatusOK,
TableColumn,
- ProgressCard,
+ GaugeCard,
TrendLine,
} from '../../components';
import { Box, Typography, Link, Chip, Grid } from '@material-ui/core';
@@ -120,14 +120,14 @@ const DataGrid = () => (
direction="row"
>
-
- {
const configApi = useApi(configApiRef);
@@ -35,42 +35,42 @@ export const DefaultProviderSettings = () => {
return (
<>
{providers.includes('google') && (
-
)}
{providers.includes('microsoft') && (
-
)}
{providers.includes('github') && (
-
)}
{providers.includes('gitlab') && (
-
)}
{providers.includes('okta') && (
-
)}
{providers.includes('oauth2') && (
- (theme => {
- return {
- root: {
- position: 'relative',
- alignSelf: 'stretch',
- },
- arrowButtonWrapper: {
- position: 'absolute',
- right: 0,
- width: ARROW_BUTTON_SIZE,
- height: ARROW_BUTTON_SIZE,
- top: -(theme.spacing(6) + ARROW_BUTTON_SIZE) / 2,
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- borderRadius: '2px 0px 0px 2px',
- background: theme.palette.pinSidebarButton.background,
- color: theme.palette.pinSidebarButton.icon,
- border: 'none',
- outline: 'none',
- cursor: 'pointer',
- },
- arrowButtonIcon: {
- transform: ({ isPinned }) => (isPinned ? 'rotate(180deg)' : 'none'),
- },
- };
-});
-
-export const SidebarPinButton: FC<{}> = () => {
- const { isOpen } = useContext(SidebarContext);
- const { isPinned, toggleSidebarPinState } = useContext(
- SidebarPinStateContext,
- );
- const classes = useStyles({ isPinned });
-
- return (
-
- {isOpen && (
-
-
-
- )}
-
- );
-};
diff --git a/packages/core/src/layout/Sidebar/Settings/AppSettingsList.tsx b/packages/core/src/layout/Sidebar/Settings/AppSettingsList.tsx
new file mode 100644
index 0000000000..5dad178ccd
--- /dev/null
+++ b/packages/core/src/layout/Sidebar/Settings/AppSettingsList.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 { List, ListSubheader } from '@material-ui/core';
+import { SidebarThemeToggle } from './ThemeToggle';
+import { SidebarPinButton } from './PinButton';
+
+export const AppSettingsList = () => (
+ App Settings}>
+
+
+
+);
diff --git a/packages/core/src/layout/Sidebar/Settings/AuthProviderList.tsx b/packages/core/src/layout/Sidebar/Settings/AuthProviderList.tsx
new file mode 100644
index 0000000000..34cf6fb837
--- /dev/null
+++ b/packages/core/src/layout/Sidebar/Settings/AuthProviderList.tsx
@@ -0,0 +1,29 @@
+/*
+ * 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 List from '@material-ui/core/List';
+import ListSubheader from '@material-ui/core/ListSubheader';
+
+type Props = {
+ providerSettings: React.ReactNode;
+};
+
+export const AuthProvidersList = ({ providerSettings }: Props) => (
+ Available Auth Providers}>
+ {providerSettings}
+
+);
diff --git a/packages/core/src/layout/Sidebar/Settings/FeatureFlagsItem.tsx b/packages/core/src/layout/Sidebar/Settings/FeatureFlagsItem.tsx
new file mode 100644
index 0000000000..3e40ce69cc
--- /dev/null
+++ b/packages/core/src/layout/Sidebar/Settings/FeatureFlagsItem.tsx
@@ -0,0 +1,73 @@
+/*
+ * 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 {
+ FeatureFlagName,
+ useApi,
+ featureFlagsApiRef,
+} from '@backstage/core-api';
+import {
+ ListItem,
+ ListItemSecondaryAction,
+ ListItemText,
+ Tooltip,
+} from '@material-ui/core';
+import CheckIcon from '@material-ui/icons/CheckCircle';
+import { ToggleButton } from '@material-ui/lab';
+
+export type Item = {
+ name: FeatureFlagName;
+ pluginId: string;
+};
+
+type Props = {
+ featureFlag: Item;
+};
+
+export const FlagItem = ({ featureFlag }: Props) => {
+ const api = useApi(featureFlagsApiRef);
+
+ const [enabled, setEnabled] = React.useState(
+ Boolean(api.getFlags().get(featureFlag.name)),
+ );
+
+ const toggleFlag = () => {
+ const newState = api.getFlags().toggle(featureFlag.name);
+ setEnabled(Boolean(newState));
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/plugins/jenkins/src/components/Layout/Layout.tsx b/packages/core/src/layout/Sidebar/Settings/FeatureFlagsList.tsx
similarity index 57%
rename from plugins/jenkins/src/components/Layout/Layout.tsx
rename to packages/core/src/layout/Sidebar/Settings/FeatureFlagsList.tsx
index c6000437a3..5687446511 100644
--- a/plugins/jenkins/src/components/Layout/Layout.tsx
+++ b/packages/core/src/layout/Sidebar/Settings/FeatureFlagsList.tsx
@@ -13,17 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
-import { Header, Page, pageTheme, HeaderLabel } from '@backstage/core';
-export const Layout: React.FC = ({ children }) => {
- return (
-
-
- {children}
-
- );
+import React from 'react';
+import List from '@material-ui/core/List';
+import ListSubheader from '@material-ui/core/ListSubheader';
+import { FlagItem, Item } from './FeatureFlagsItem';
+
+type Props = {
+ featureFlags: Item[];
};
+
+export const FeatureFlagsList = ({ featureFlags }: Props) => (
+ Feature Flags}>
+ {featureFlags.map(featureFlag => (
+
+ ))}
+
+);
diff --git a/packages/core/src/layout/Sidebar/Settings/OAuthProviderSettings.tsx b/packages/core/src/layout/Sidebar/Settings/OAuthProviderSettings.tsx
deleted file mode 100644
index 73659eb63d..0000000000
--- a/packages/core/src/layout/Sidebar/Settings/OAuthProviderSettings.tsx
+++ /dev/null
@@ -1,80 +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 {
- ApiRef,
- OAuthApi,
- SessionStateApi,
- useApi,
- Subscription,
- IconComponent,
- SessionState,
-} from '@backstage/core-api';
-import React, { FC, useState, useEffect } from 'react';
-import { ProviderSettingsItem } from './ProviderSettingsItem';
-
-type OAuthProviderSidebarProps = {
- title: string;
- icon: IconComponent;
- apiRef: ApiRef;
-};
-
-export const OAuthProviderSettings: FC = ({
- title,
- icon,
- apiRef,
-}) => {
- const api = useApi(apiRef);
- const [signedIn, setSignedIn] = useState(false);
-
- useEffect(() => {
- let didCancel = false;
-
- const checkSession = async () => {
- const session = await api.getAccessToken('', { optional: true });
- if (!didCancel) {
- setSignedIn(!!session);
- }
- };
- let subscription: Subscription;
- const observeSession = () => {
- subscription = api
- .sessionState$()
- .subscribe((sessionState: SessionState) => {
- if (!didCancel) {
- setSignedIn(sessionState === SessionState.SignedIn);
- }
- });
- };
-
- checkSession();
- observeSession();
- return () => {
- didCancel = true;
- subscription.unsubscribe();
- };
- }, [api]);
-
- return (
- api.getAccessToken()}
- />
- );
-};
diff --git a/packages/core/src/layout/Sidebar/Settings/OIDCProviderSettings.tsx b/packages/core/src/layout/Sidebar/Settings/OIDCProviderSettings.tsx
deleted file mode 100644
index 19ee00eee1..0000000000
--- a/packages/core/src/layout/Sidebar/Settings/OIDCProviderSettings.tsx
+++ /dev/null
@@ -1,81 +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 {
- ApiRef,
- OpenIdConnectApi,
- SessionStateApi,
- useApi,
- Subscription,
- IconComponent,
- SessionState,
-} from '@backstage/core-api';
-import React, { FC, useState, useEffect } from 'react';
-import { ProviderSettingsItem } from './ProviderSettingsItem';
-
-export type OIDCProviderSidebarProps = {
- title: string;
- icon: IconComponent;
- apiRef: ApiRef;
-};
-
-export const OIDCProviderSettings: FC = ({
- title,
- icon,
- apiRef,
-}) => {
- const api = useApi(apiRef);
- const [signedIn, setSignedIn] = useState(false);
-
- useEffect(() => {
- let didCancel = false;
-
- const checkSession = async () => {
- const session = await api.getIdToken({ optional: true });
- if (!didCancel) {
- setSignedIn(!!session);
- }
- };
-
- let subscription: Subscription;
- const observeSession = () => {
- subscription = api
- .sessionState$()
- .subscribe((sessionState: SessionState) => {
- if (!didCancel) {
- setSignedIn(sessionState === SessionState.SignedIn);
- }
- });
- };
-
- checkSession();
- observeSession();
- return () => {
- didCancel = true;
- subscription.unsubscribe();
- };
- }, [api]);
-
- return (
- api.getIdToken()}
- />
- );
-};
diff --git a/packages/core/src/layout/Sidebar/Settings/PinButton.tsx b/packages/core/src/layout/Sidebar/Settings/PinButton.tsx
new file mode 100644
index 0000000000..2727313ede
--- /dev/null
+++ b/packages/core/src/layout/Sidebar/Settings/PinButton.tsx
@@ -0,0 +1,64 @@
+/*
+ * 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, { useContext } from 'react';
+import {
+ ListItem,
+ ListItemSecondaryAction,
+ ListItemText,
+ Tooltip,
+} from '@material-ui/core';
+import LockIcon from '@material-ui/icons/Lock';
+import LockOpenIcon from '@material-ui/icons/LockOpen';
+import { ToggleButton } from '@material-ui/lab';
+import { SidebarPinStateContext } from '../Page';
+
+export const SidebarPinButton = () => {
+ const { isPinned, toggleSidebarPinState } = useContext(
+ SidebarPinStateContext,
+ );
+
+ const PinIcon = () => (
+
+ {isPinned ? : }
+
+ );
+
+ return (
+
+
+
+ {
+ toggleSidebarPinState();
+ }}
+ >
+
+
+
+
+ );
+};
diff --git a/packages/core/src/layout/Sidebar/Settings/ProviderSettingsItem.tsx b/packages/core/src/layout/Sidebar/Settings/ProviderSettingsItem.tsx
index 3d3d7439e1..4db7ea0823 100644
--- a/packages/core/src/layout/Sidebar/Settings/ProviderSettingsItem.tsx
+++ b/packages/core/src/layout/Sidebar/Settings/ProviderSettingsItem.tsx
@@ -14,31 +14,77 @@
* limitations under the License.
*/
-import React, { FC } from 'react';
-import { OAuthApi, OpenIdConnectApi, IconComponent } from '@backstage/core-api';
-import { SidebarItem } from '../Items';
-import { IconButton, Tooltip } from '@material-ui/core';
-import StarBorder from '@material-ui/icons/StarBorder';
+import React, { FC, useState, useEffect } from 'react';
+import {
+ ListItem,
+ ListItemIcon,
+ ListItemSecondaryAction,
+ ListItemText,
+ Tooltip,
+} from '@material-ui/core';
import PowerButton from '@material-ui/icons/PowerSettingsNew';
+import { ToggleButton } from '@material-ui/lab';
+import {
+ ApiRef,
+ SessionApi,
+ useApi,
+ IconComponent,
+ SessionState,
+} from '@backstage/core-api';
-export const ProviderSettingsItem: FC<{
+type OAuthProviderSidebarProps = {
title: string;
icon: IconComponent;
- signedIn: boolean;
- api: OAuthApi | OpenIdConnectApi;
- signInHandler: Function;
-}> = ({ title, icon, signedIn, api, signInHandler }) => {
+ apiRef: ApiRef;
+};
+
+export const ProviderSettingsItem: FC = ({
+ title,
+ icon: Icon,
+ apiRef,
+}) => {
+ const api = useApi(apiRef);
+ const [signedIn, setSignedIn] = useState(false);
+
+ useEffect(() => {
+ let didCancel = false;
+
+ const subscription = api
+ .sessionState$()
+ .subscribe((sessionState: SessionState) => {
+ if (!didCancel) {
+ setSignedIn(sessionState === SessionState.SignedIn);
+ }
+ });
+
+ return () => {
+ didCancel = true;
+ subscription.unsubscribe();
+ };
+ }, [api]);
+
return (
-
- (signedIn ? api.logout() : signInHandler())}>
-
+
+
+
+
+
+ (signedIn ? api.signOut() : api.signIn())}
>
-
-
-
-
+
+
+
+
+
+
);
};
diff --git a/packages/core/src/layout/Sidebar/Settings/SettingsDialog.tsx b/packages/core/src/layout/Sidebar/Settings/SettingsDialog.tsx
new file mode 100644
index 0000000000..71c4899862
--- /dev/null
+++ b/packages/core/src/layout/Sidebar/Settings/SettingsDialog.tsx
@@ -0,0 +1,74 @@
+/*
+ * 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 {
+ Card,
+ CardContent,
+ CardHeader,
+ makeStyles,
+ Divider,
+} from '@material-ui/core';
+import { AppSettingsList } from './AppSettingsList';
+import { AuthProvidersList } from './AuthProviderList';
+import { FeatureFlagsList } from './FeatureFlagsList';
+import { SignInAvatar } from './SignInAvatar';
+import { UserSettingsMenu } from './UserSettingsMenu';
+import { useUserProfile } from './useUserProfileInfo';
+import { useApi, featureFlagsApiRef } from '@backstage/core-api';
+
+const useStyles = makeStyles({
+ root: {
+ minWidth: 400,
+ },
+});
+
+type Props = {
+ providerSettings?: React.ReactNode;
+};
+
+export const SettingsDialog = ({ providerSettings }: Props) => {
+ const classes = useStyles();
+ const { profile, displayName } = useUserProfile();
+ const featureFlagsApi = useApi(featureFlagsApiRef);
+ const featureFlags = featureFlagsApi.getRegisteredFlags();
+
+ return (
+
+ }
+ action={ }
+ title={displayName}
+ subheader={profile.email}
+ />
+
+
+ {providerSettings && (
+ <>
+
+
+ >
+ )}
+ {featureFlags.length > 0 && (
+ <>
+
+
+ >
+ )}
+
+
+ );
+};
diff --git a/plugins/api-docs/src/components/ApiDefinitionWidget/ApiDefinitionWidget.tsx b/packages/core/src/layout/Sidebar/Settings/SignInAvatar.tsx
similarity index 50%
rename from plugins/api-docs/src/components/ApiDefinitionWidget/ApiDefinitionWidget.tsx
rename to packages/core/src/layout/Sidebar/Settings/SignInAvatar.tsx
index e1283bb525..f0430edfcf 100644
--- a/plugins/api-docs/src/components/ApiDefinitionWidget/ApiDefinitionWidget.tsx
+++ b/packages/core/src/layout/Sidebar/Settings/SignInAvatar.tsx
@@ -15,26 +15,28 @@
*/
import React from 'react';
-import { AsyncApiDefinitionWidget } from '../AsyncApiDefinitionWidget';
-import { OpenApiDefinitionWidget } from '../OpenApiDefinitionWidget';
-import { PlainApiDefinitionWidget } from '../PlainApiDefinitionWidget';
+import { BackstageTheme } from '@backstage/theme';
+import { makeStyles, Avatar } from '@material-ui/core';
+import { useUserProfile } from './useUserProfileInfo';
+import { sidebarConfig } from '../config';
-type Props = {
- type: string;
- definition: string;
-};
-
-export const ApiDefinitionWidget = ({ type, definition }: Props) => {
- switch (type) {
- case 'openapi':
- return ;
-
- case 'asyncapi':
- return ;
-
- default:
- return (
-
- );
- }
+const useStyles = makeStyles({
+ avatar: {
+ width: ({ size }) => size,
+ height: ({ size }) => size,
+ },
+});
+
+type Props = { size?: number };
+
+export const SignInAvatar = ({ size }: Props) => {
+ const { iconSize } = sidebarConfig;
+ const classes = useStyles(size ? { size } : { size: iconSize });
+ const { profile, displayName } = useUserProfile();
+
+ return (
+
+ {displayName[0]}
+
+ );
};
diff --git a/packages/core/src/layout/Sidebar/Settings/ThemeToggle.tsx b/packages/core/src/layout/Sidebar/Settings/ThemeToggle.tsx
new file mode 100644
index 0000000000..a5e703089c
--- /dev/null
+++ b/packages/core/src/layout/Sidebar/Settings/ThemeToggle.tsx
@@ -0,0 +1,87 @@
+/*
+ * 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 { useObservable } from 'react-use';
+import LightIcon from '@material-ui/icons/WbSunny';
+import DarkIcon from '@material-ui/icons/Brightness2';
+import AutoIcon from '@material-ui/icons/BrightnessAuto';
+import { appThemeApiRef, useApi } from '@backstage/core-api';
+import ToggleButton from '@material-ui/lab/ToggleButton';
+import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
+import {
+ ListItem,
+ ListItemText,
+ ListItemSecondaryAction,
+ Tooltip,
+} from '@material-ui/core';
+
+export const SidebarThemeToggle = () => {
+ const appThemeApi = useApi(appThemeApiRef);
+ const themeId = useObservable(
+ appThemeApi.activeThemeId$(),
+ appThemeApi.getActiveThemeId(),
+ );
+
+ const themeIds = appThemeApi.getInstalledThemes();
+ // TODO(marcuseide): can these be put on the theme itself?
+ const themeIcons = {
+ dark: ,
+ light: ,
+ };
+
+ const handleSetTheme = (
+ _event: React.MouseEvent,
+ newThemeId: string | undefined,
+ ) => {
+ if (themeIds.some(t => t.id === newThemeId)) {
+ appThemeApi.setActiveThemeId(newThemeId);
+ } else {
+ appThemeApi.setActiveThemeId(undefined);
+ }
+ };
+
+ return (
+
+
+
+
+ {themeIds.map(theme => (
+
+
+ {themeIcons[theme.variant]}
+
+
+ ))}
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/core/src/layout/Sidebar/Settings/UserProfile.tsx b/packages/core/src/layout/Sidebar/Settings/UserProfile.tsx
deleted file mode 100644
index 3b854801c9..0000000000
--- a/packages/core/src/layout/Sidebar/Settings/UserProfile.tsx
+++ /dev/null
@@ -1,61 +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, useRef } from 'react';
-import { makeStyles, Avatar, Divider } from '@material-ui/core';
-import { useApi, identityApiRef } from '@backstage/core-api';
-import { SidebarItem } from '../Items';
-import ExpandLess from '@material-ui/icons/ExpandLess';
-import ExpandMore from '@material-ui/icons/ExpandMore';
-
-const useStyles = makeStyles({
- avatar: {
- width: 24,
- height: 24,
- },
-});
-
-export const UserProfile: FC<{ open: boolean; setOpen: Function }> = ({
- open,
- setOpen,
-}) => {
- const ref = useRef(); // for scrolling down when collapse item opens
- const classes = useStyles();
- const identityApi = useApi(identityApiRef);
-
- const handleClick = () => {
- setOpen(!open);
- setTimeout(() => ref.current?.scrollIntoView({ behavior: 'smooth' }), 300);
- };
-
- const userId = identityApi.getUserId();
- const profile = identityApi.getProfile();
- const displayName = profile.displayName ?? userId;
- const SignInAvatar = () => (
-
- {displayName[0]}
-
- );
-
- return (
- <>
-
-
- {open ? : }
-
- >
- );
-};
diff --git a/packages/core/src/layout/Sidebar/Settings/UserSettings.tsx b/packages/core/src/layout/Sidebar/Settings/UserSettings.tsx
new file mode 100644
index 0000000000..85dbefff9b
--- /dev/null
+++ b/packages/core/src/layout/Sidebar/Settings/UserSettings.tsx
@@ -0,0 +1,77 @@
+/*
+ * 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, { useEffect, useContext } from 'react';
+import { Popover } from '@material-ui/core';
+import { SignInAvatar } from './SignInAvatar';
+import { SettingsDialog } from './SettingsDialog';
+import { SidebarItem } from '../Items';
+import { useUserProfile } from './useUserProfileInfo';
+import { SidebarContext } from '../config';
+
+type Props = {
+ providerSettings?: React.ReactNode;
+};
+
+export const SidebarUserSettings = ({ providerSettings }: Props) => {
+ const { isOpen: sidebarOpen } = useContext(SidebarContext);
+ const { displayName } = useUserProfile();
+ const [open, setOpen] = React.useState(false);
+ const [anchorEl, setAnchorEl] = React.useState(
+ undefined,
+ );
+
+ const handleOpen = (event?: React.MouseEvent) => {
+ setAnchorEl(event?.currentTarget ?? undefined);
+ setOpen(true);
+ };
+
+ const handleClose = () => {
+ setAnchorEl(undefined);
+ setOpen(false);
+ };
+
+ useEffect(() => {
+ if (!sidebarOpen && open) setOpen(false);
+ }, [open, sidebarOpen]);
+
+ const SidebarAvatar = () => ;
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
diff --git a/packages/core/src/layout/Sidebar/Settings/UserSettingsMenu.tsx b/packages/core/src/layout/Sidebar/Settings/UserSettingsMenu.tsx
new file mode 100644
index 0000000000..151ddb6e75
--- /dev/null
+++ b/packages/core/src/layout/Sidebar/Settings/UserSettingsMenu.tsx
@@ -0,0 +1,55 @@
+/*
+ * 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 { identityApiRef, useApi } from '@backstage/core-api';
+import { IconButton, ListItemIcon, Menu, MenuItem } from '@material-ui/core';
+import SignOutIcon from '@material-ui/icons/MeetingRoom';
+import MoreVertIcon from '@material-ui/icons/MoreVert';
+
+export const UserSettingsMenu = () => {
+ const identityApi = useApi(identityApiRef);
+ const [open, setOpen] = React.useState(false);
+ const [anchorEl, setAnchorEl] = React.useState(
+ undefined,
+ );
+
+ const handleOpen = (event: React.MouseEvent) => {
+ setAnchorEl(event.currentTarget);
+ setOpen(true);
+ };
+
+ const handleClose = () => {
+ setAnchorEl(undefined);
+ setOpen(false);
+ };
+
+ return (
+ <>
+
+
+
+
+ identityApi.signOut()}>
+
+
+
+ Sign Out
+
+
+ >
+ );
+};
diff --git a/packages/core/src/layout/Sidebar/Settings/index.ts b/packages/core/src/layout/Sidebar/Settings/index.ts
index 6557ace53a..15042dc85d 100644
--- a/packages/core/src/layout/Sidebar/Settings/index.ts
+++ b/packages/core/src/layout/Sidebar/Settings/index.ts
@@ -15,6 +15,4 @@
*/
export { ProviderSettingsItem } from './ProviderSettingsItem';
-export { OAuthProviderSettings } from './OAuthProviderSettings';
-export { OIDCProviderSettings } from './OIDCProviderSettings';
-export { UserProfile } from './UserProfile';
+export { SidebarUserSettings } from './UserSettings';
diff --git a/packages/core/src/layout/Sidebar/Settings/useUserProfileInfo.ts b/packages/core/src/layout/Sidebar/Settings/useUserProfileInfo.ts
new file mode 100644
index 0000000000..60dae294a5
--- /dev/null
+++ b/packages/core/src/layout/Sidebar/Settings/useUserProfileInfo.ts
@@ -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 { useApi, identityApiRef } from '@backstage/core-api';
+
+export const useUserProfile = () => {
+ const identityApi = useApi(identityApiRef);
+ const userId = identityApi.getUserId();
+ const profile = identityApi.getProfile();
+ const displayName = profile.displayName ?? userId;
+
+ return { profile, displayName };
+};
diff --git a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx
index d7451965fe..61975a728a 100644
--- a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx
+++ b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx
@@ -23,7 +23,7 @@ import {
SidebarSearchField,
SidebarSpace,
SidebarUserSettings,
- OAuthProviderSettings,
+ ProviderSettingsItem,
} from '.';
import HomeOutlinedIcon from '@material-ui/icons/HomeOutlined';
import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline';
@@ -60,7 +60,7 @@ export const SampleSidebar = () => (
= () => {
- const appThemeApi = useApi(appThemeApiRef);
- const themeId = useObservable(
- appThemeApi.activeThemeId$(),
- appThemeApi.getActiveThemeId(),
- );
-
- let text = 'Auto';
- let icon = AutoIcon;
- switch (themeId) {
- case 'dark':
- text = 'Dark mode';
- icon = DarkIcon;
- break;
- case 'light':
- text = 'Light mode';
- icon = LightIcon;
- break;
- default:
- break;
- }
-
- const handleToggle = () => {
- if (!themeId) {
- appThemeApi.setActiveThemeId('light');
- } else if (themeId === 'light') {
- appThemeApi.setActiveThemeId('dark');
- } else {
- appThemeApi.setActiveThemeId(undefined);
- }
- };
-
- return ;
-};
diff --git a/packages/core/src/layout/Sidebar/UserSettings.tsx b/packages/core/src/layout/Sidebar/UserSettings.tsx
deleted file mode 100644
index f586f6d077..0000000000
--- a/packages/core/src/layout/Sidebar/UserSettings.tsx
+++ /dev/null
@@ -1,53 +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 { identityApiRef, useApi } from '@backstage/core-api';
-import Collapse from '@material-ui/core/Collapse';
-import SignOutIcon from '@material-ui/icons/MeetingRoom';
-import React, { useContext, useEffect } from 'react';
-import { SidebarContext } from './config';
-import { SidebarItem } from './Items';
-import { UserProfile as SidebarUserProfile } from './Settings';
-
-type SidebarUserSettingsProps = { providerSettings?: React.ReactNode };
-
-export function SidebarUserSettings({
- providerSettings,
-}: SidebarUserSettingsProps) {
- const { isOpen: sidebarOpen } = useContext(SidebarContext);
- const [open, setOpen] = React.useState(false);
- const identityApi = useApi(identityApiRef);
-
- // Close the provider list when sidebar collapse
- useEffect(() => {
- if (!sidebarOpen && open) setOpen(false);
- }, [open, sidebarOpen]);
-
- return (
- <>
-
-
- {providerSettings}
-
- identityApi.logout()}
- />
-
- >
- );
-}
diff --git a/packages/core/src/layout/Sidebar/index.ts b/packages/core/src/layout/Sidebar/index.ts
index fdca7e4eea..a644c06e14 100644
--- a/packages/core/src/layout/Sidebar/index.ts
+++ b/packages/core/src/layout/Sidebar/index.ts
@@ -25,14 +25,11 @@ export {
SidebarSpacer,
} from './Items';
export { IntroCard, SidebarIntro } from './Intro';
-export { SidebarPinButton } from './PinButton';
export {
SIDEBAR_INTRO_LOCAL_STORAGE,
SidebarContext,
sidebarConfig,
} from './config';
export type { SidebarContextType } from './config';
-export { SidebarThemeToggle } from './SidebarThemeToggle';
-export { SidebarUserSettings } from './UserSettings';
export { DefaultProviderSettings } from './DefaultProviderSettings';
export * from './Settings';
diff --git a/packages/core/src/layout/SignInPage/auth0Provider.tsx b/packages/core/src/layout/SignInPage/auth0Provider.tsx
index ae4e5f8b82..423b28ea8d 100644
--- a/packages/core/src/layout/SignInPage/auth0Provider.tsx
+++ b/packages/core/src/layout/SignInPage/auth0Provider.tsx
@@ -37,8 +37,8 @@ const Component: ProviderComponent = ({ onResult }) => {
profile: profile!,
getIdToken: () =>
auth0AuthApi.getBackstageIdentity().then(i => i!.idToken),
- logout: async () => {
- await auth0AuthApi.logout();
+ signOut: async () => {
+ await auth0AuthApi.signOut();
},
});
} catch (error) {
@@ -79,8 +79,8 @@ const loader: ProviderLoader = async apis => {
userId: identity.id,
profile: profile!,
getIdToken: () => auth0AuthApi.getBackstageIdentity().then(i => i!.idToken),
- logout: async () => {
- await auth0AuthApi.logout();
+ signOut: async () => {
+ await auth0AuthApi.signOut();
},
};
};
diff --git a/packages/core/src/layout/SignInPage/commonProvider.tsx b/packages/core/src/layout/SignInPage/commonProvider.tsx
index de9452a850..b7480afa49 100644
--- a/packages/core/src/layout/SignInPage/commonProvider.tsx
+++ b/packages/core/src/layout/SignInPage/commonProvider.tsx
@@ -44,8 +44,8 @@ const Component: ProviderComponent = ({ config, onResult }) => {
getIdToken: () => {
return authApi.getBackstageIdentity().then(i => i!.idToken);
},
- logout: async () => {
- await authApi.logout();
+ signOut: async () => {
+ await authApi.signOut();
},
});
} catch (error) {
@@ -87,8 +87,8 @@ const loader: ProviderLoader = async (apis, apiRef) => {
userId: identity.id,
profile: profile!,
getIdToken: () => authApi.getBackstageIdentity().then(i => i!.idToken),
- logout: async () => {
- await authApi.logout();
+ signOut: async () => {
+ await authApi.signOut();
},
};
};
diff --git a/packages/core/src/layout/SignInPage/providers.tsx b/packages/core/src/layout/SignInPage/providers.tsx
index ff60eba228..e2c17ab80d 100644
--- a/packages/core/src/layout/SignInPage/providers.tsx
+++ b/packages/core/src/layout/SignInPage/providers.tsx
@@ -83,14 +83,14 @@ export const useSignInProviders = (
const apiHolder = useApiHolder();
const [loading, setLoading] = useState(true);
- // This decorates the result with logout logic from this hook
+ // This decorates the result with sign out logic from this hook
const handleWrappedResult = useCallback(
(result: SignInResult) => {
onResult({
...result,
- logout: async () => {
+ signOut: async () => {
localStorage.removeItem(PROVIDER_STORAGE_KEY);
- await result.logout?.();
+ await result.signOut?.();
},
});
},
diff --git a/packages/core/src/layout/SignInPage/types.ts b/packages/core/src/layout/SignInPage/types.ts
index 9c501e8095..48945e6988 100644
--- a/packages/core/src/layout/SignInPage/types.ts
+++ b/packages/core/src/layout/SignInPage/types.ts
@@ -20,19 +20,16 @@ import {
SignInResult,
ApiHolder,
ApiRef,
- OAuthApi,
ProfileInfoApi,
BackstageIdentityApi,
- SessionStateApi,
+ SessionApi,
} from '@backstage/core-api';
export type SignInConfig = {
id: string;
title: string;
message: string;
- apiRef: ApiRef<
- OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi
- >;
+ apiRef: ApiRef;
};
export type IdentityProviders = ('guest' | 'custom' | SignInConfig)[];
@@ -43,9 +40,7 @@ export type ProviderComponent = ComponentType<
export type ProviderLoader = (
apis: ApiHolder,
- apiRef: ApiRef<
- OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi
- >,
+ apiRef: ApiRef,
) => Promise;
export type SignInProvider = {
diff --git a/packages/create-app/package.json b/packages/create-app/package.json
index af7b78eb74..46ccfa0b40 100644
--- a/packages/create-app/package.json
+++ b/packages/create-app/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/create-app",
"description": "Create app package for Backstage",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public"
@@ -27,7 +27,7 @@
"start": "nodemon --"
},
"dependencies": {
- "@backstage/cli-common": "^0.1.1-alpha.21",
+ "@backstage/cli-common": "^0.1.1-alpha.23",
"chalk": "^4.0.0",
"commander": "^6.1.0",
"fs-extra": "^9.0.0",
diff --git a/packages/create-app/templates/default-app/.gitignore b/packages/create-app/templates/default-app/.gitignore.hbs
similarity index 96%
rename from packages/create-app/templates/default-app/.gitignore
rename to packages/create-app/templates/default-app/.gitignore.hbs
index 4f9065c60b..5f5cc739f4 100644
--- a/packages/create-app/templates/default-app/.gitignore
+++ b/packages/create-app/templates/default-app/.gitignore.hbs
@@ -1,4 +1,3 @@
-
# Logs
logs
*.log
@@ -31,4 +30,4 @@ dist-types
site
# Local configuration files
-*.local.yaml
+*.local.yaml
\ No newline at end of file
diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs
index bdd933e810..a7d7aa8010 100644
--- a/packages/create-app/templates/default-app/app-config.yaml.hbs
+++ b/packages/create-app/templates/default-app/app-config.yaml.hbs
@@ -15,6 +15,7 @@ backend:
connection: ':memory:'
{{/if}}
{{#if dbTypePG}}
+ # config options: https://node-postgres.com/api/client
database:
client: pg
connection:
@@ -30,6 +31,11 @@ backend:
password:
$secret:
env: POSTGRES_PASSWORD
+ # https://node-postgres.com/features/ssl
+ #ssl: require # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require)
+ #ca: # if you have a CA file and want to verify it you can uncomment this section
+ # $secret:
+ # file: /ca/server.crt
{{/if}}
proxy:
@@ -40,6 +46,8 @@ proxy:
techdocs:
storageUrl: http://localhost:7000/techdocs/static/docs
requestUrl: http://localhost:7000/techdocs/docs
+ generators:
+ techdocs: 'docker'
lighthouse:
baseUrl: http://localhost:3003
@@ -47,29 +55,38 @@ lighthouse:
auth:
providers: {}
+scaffolder:
+ github:
+ token:
+ $secret:
+ env: GITHUB_ACCESS_TOKEN
+ visibility: public # or 'internal' or 'private'
+
catalog:
+ rules:
+ - allow: [Component, API, Group, Template, Location]
+ processors:
+ github:
+ providers:
+ - target: https://github.com
+ token:
+ $secret:
+ env: GITHUB_PRIVATE_TOKEN
+ # Example for how to add your GitHub Enterprise instance:
+ # - target: https://ghe.example.net
+ # apiBaseUrl: https://ghe.example.net/api/v3
+ # token:
+ # $secret:
+ # env: GHE_PRIVATE_TOKEN
locations:
# Backstage example components
- type: github
- target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/artist-lookup-component.yaml
+ target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/all-components.yaml
+
+ # Backstage example APIs
- type: github
- target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/playback-order-component.yaml
- - type: github
- target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/podcast-api-component.yaml
- - type: github
- target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/queue-proxy-component.yaml
- - type: github
- target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/searcher-component.yaml
- - type: github
- target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/playback-lib-component.yaml
- - type: github
- target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/www-artist-component.yaml
- - type: github
- target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/shuffle-api-component.yaml
- - type: github
- target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/hello-world-api.yaml
- - type: github
- target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/streetlights-api.yaml
+ target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml
+
# Backstage example templates
- type: github
target: https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml
diff --git a/packages/create-app/templates/default-app/package.json.hbs b/packages/create-app/templates/default-app/package.json.hbs
index b95b5bbc5b..19e3891551 100644
--- a/packages/create-app/templates/default-app/package.json.hbs
+++ b/packages/create-app/templates/default-app/package.json.hbs
@@ -16,7 +16,7 @@
"test:all": "lerna run test -- --coverage",
"lint": "lerna run lint --since origin/master --",
"lint:all": "lerna run lint --",
- "create-plugin": "backstage-cli create-plugin",
+ "create-plugin": "backstage-cli create-plugin --scope backstage --no-private",
"remove-plugin": "backstage-cli remove-plugin"
},
"workspaces": {
diff --git a/packages/create-app/templates/default-app/packages/app/public/android-chrome-192x192.png b/packages/create-app/templates/default-app/packages/app/public/android-chrome-192x192.png
index 3d56edbb0d..4660f988c1 100644
Binary files a/packages/create-app/templates/default-app/packages/app/public/android-chrome-192x192.png and b/packages/create-app/templates/default-app/packages/app/public/android-chrome-192x192.png differ
diff --git a/packages/create-app/templates/default-app/packages/app/public/apple-touch-icon.png b/packages/create-app/templates/default-app/packages/app/public/apple-touch-icon.png
index 0977175f6f..57c05cfc9a 100644
Binary files a/packages/create-app/templates/default-app/packages/app/public/apple-touch-icon.png and b/packages/create-app/templates/default-app/packages/app/public/apple-touch-icon.png differ
diff --git a/packages/create-app/templates/default-app/packages/app/public/favicon-16x16.png b/packages/create-app/templates/default-app/packages/app/public/favicon-16x16.png
index a455ffac7b..58cf61a35e 100644
Binary files a/packages/create-app/templates/default-app/packages/app/public/favicon-16x16.png and b/packages/create-app/templates/default-app/packages/app/public/favicon-16x16.png differ
diff --git a/packages/create-app/templates/default-app/packages/app/public/favicon-32x32.png b/packages/create-app/templates/default-app/packages/app/public/favicon-32x32.png
index e2707f2d1e..c0915ece75 100644
Binary files a/packages/create-app/templates/default-app/packages/app/public/favicon-32x32.png and b/packages/create-app/templates/default-app/packages/app/public/favicon-32x32.png differ
diff --git a/packages/create-app/templates/default-app/packages/app/public/favicon.ico b/packages/create-app/templates/default-app/packages/app/public/favicon.ico
index 5b582704a1..5e45e5dfbd 100644
Binary files a/packages/create-app/templates/default-app/packages/app/public/favicon.ico and b/packages/create-app/templates/default-app/packages/app/public/favicon.ico differ
diff --git a/packages/create-app/templates/default-app/packages/app/src/LogoFull.tsx b/packages/create-app/templates/default-app/packages/app/src/LogoFull.tsx
new file mode 100644
index 0000000000..d2b1bf1080
--- /dev/null
+++ b/packages/create-app/templates/default-app/packages/app/src/LogoFull.tsx
@@ -0,0 +1,46 @@
+/*
+ * 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 { makeStyles } from '@material-ui/core';
+
+const useStyles = makeStyles({
+ svg: {
+ width: 'auto',
+ height: 30,
+ },
+ path: {
+ fill: '#7df3e1',
+ },
+});
+const LogoFull: FC<{}> = () => {
+ const classes = useStyles();
+
+ return (
+
+
+
+ );
+};
+
+export default LogoFull;
diff --git a/packages/create-app/templates/default-app/packages/app/src/LogoIcon.tsx b/packages/create-app/templates/default-app/packages/app/src/LogoIcon.tsx
new file mode 100644
index 0000000000..d70be3dd32
--- /dev/null
+++ b/packages/create-app/templates/default-app/packages/app/src/LogoIcon.tsx
@@ -0,0 +1,47 @@
+/*
+ * 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 { makeStyles } from '@material-ui/core';
+
+const useStyles = makeStyles({
+ svg: {
+ width: 'auto',
+ height: 28,
+ },
+ path: {
+ fill: '#7df3e1',
+ },
+});
+
+const LogoIcon: FC<{}> = () => {
+ const classes = useStyles();
+
+ return (
+
+
+
+ );
+};
+
+export default LogoIcon;
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 9e343b86ac..9dd9ea64c3 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
@@ -1,24 +1,29 @@
-import React from 'react';
+import React, { FC, useContext } from 'react';
import HomeIcon from '@material-ui/icons/Home';
import LibraryBooks from '@material-ui/icons/LibraryBooks';
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
import BuildIcon from '@material-ui/icons/BuildRounded';
import RuleIcon from '@material-ui/icons/AssignmentTurnedIn';
import MapIcon from '@material-ui/icons/MyLocation';
+import { Link, makeStyles } from '@material-ui/core';
+import { NavLink } from 'react-router-dom';
+import LogoFull from './LogoFull';
+import LogoIcon from './LogoIcon';
import {
Sidebar,
SidebarItem,
SidebarDivider,
+ sidebarConfig,
+ SidebarContext,
SidebarSpace,
SidebarUserSettings,
- SidebarThemeToggle,
- SidebarPinButton,
DefaultProviderSettings,
} from '@backstage/core';
export const AppSidebar = () => (
+
{/* Global nav, not org-specific */}
@@ -32,8 +37,39 @@ export const AppSidebar = () => (
-
} />
-
);
+
+const useSidebarLogoStyles = makeStyles({
+ root: {
+ width: sidebarConfig.drawerWidthClosed,
+ height: 3 * sidebarConfig.logoHeight,
+ display: 'flex',
+ flexFlow: 'row nowrap',
+ alignItems: 'center',
+ marginBottom: -14,
+ },
+ link: {
+ width: sidebarConfig.drawerWidthClosed,
+ marginLeft: 24,
+ },
+});
+
+const SidebarLogo: FC<{}> = () => {
+ const classes = useSidebarLogoStyles();
+ const { isOpen } = useContext(SidebarContext);
+
+ return (
+
+
+ {isOpen ? : }
+
+
+ );
+};
diff --git a/packages/create-app/templates/default-app/packages/backend/Dockerfile b/packages/create-app/templates/default-app/packages/backend/Dockerfile
index 3e8ba36cec..a7bd814b19 100644
--- a/packages/create-app/templates/default-app/packages/backend/Dockerfile
+++ b/packages/create-app/templates/default-app/packages/backend/Dockerfile
@@ -2,8 +2,15 @@ FROM node:12
WORKDIR /usr/src/app
-COPY . .
+# 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 . .
+
CMD ["node", "packages/backend"]
diff --git a/packages/create-app/templates/default-app/packages/backend/README.md b/packages/create-app/templates/default-app/packages/backend/README.md
index f94904a930..5583bff625 100644
--- a/packages/create-app/templates/default-app/packages/backend/README.md
+++ b/packages/create-app/templates/default-app/packages/backend/README.md
@@ -3,8 +3,8 @@
This package is an EXAMPLE of a Backstage backend.
The main purpose of this package is to provide a test bed for Backstage plugins
-that have a backend part. Feel free to experiment locally or within your fork
-by adding dependencies and routes to this backend, to try things out.
+that have a backend part. Feel free to experiment locally or within your fork by
+adding dependencies and routes to this backend, to try things out.
Our goal is to eventually amend the create-app flow of the CLI, such that a
production ready version of a backend skeleton is made alongside the frontend
@@ -33,34 +33,32 @@ LOG_LEVEL=debug \
yarn start
```
-Substitute `x` for actual values, or leave them as
-dummy values just to try out the backend without using the auth or sentry features.
+Substitute `x` for actual values, or leave them as dummy values just to try out
+the backend without using the auth or sentry features.
The backend starts up on port 7000 per default.
## Populating The Catalog
-If you want to use the catalog functionality, you need to add so called locations
-to the backend. These are places where the backend can find some entity descriptor
-data to consume and serve.
+If you want to use the catalog functionality, you need to add so called
+locations to the backend. These are places where the backend can find some
+entity descriptor data to consume and serve. For more information, see
+[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog).
-To get started, you can issue the following after starting the backend, from inside
-the `plugins/catalog-backend` directory:
-
-```bash
-yarn mock-data
-```
-
-You should then start seeing data on `localhost:7000/catalog/entities`.
-
-The catalog currently runs in-memory only, so feel free to try it out, but it will
-need to be re-populated on next startup.
+To get started quickly, this template already includes some statically configured example locations
+in `app-config.yaml` under `catalog.locations`. You can remove and replace these locations as you
+like, and also override them for local development in `app-config.local.yaml`.
## Authentication
-We chose [Passport](http://www.passportjs.org/) as authentication platform due to its comprehensive set of supported authentication [strategies](http://www.passportjs.org/packages/).
+We chose [Passport](http://www.passportjs.org/) as authentication platform due
+to its comprehensive set of supported authentication
+[strategies](http://www.passportjs.org/packages/).
-Read more about the [auth-backend](https://github.com/spotify/backstage/blob/master/plugins/auth-backend/README.md) and [how to add a new provider](https://github.com/spotify/backstage/blob/master/docs/auth/add-auth-provider.md)
+Read more about the
+[auth-backend](https://github.com/spotify/backstage/blob/master/plugins/auth-backend/README.md)
+and
+[how to add a new provider](https://github.com/spotify/backstage/blob/master/docs/auth/add-auth-provider.md)
## Documentation
diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs
index 044ee1560a..adde81e830 100644
--- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs
+++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs
@@ -28,6 +28,7 @@
"@backstage/plugin-scaffolder-backend": "^{{version}}",
"@backstage/plugin-techdocs-backend": "^{{version}}",
"@octokit/rest": "^18.0.0",
+ "@gitbeaker/node": "^23.5.0",
"dockerode": "^3.2.0",
"express": "^4.17.1",
"knex": "^0.21.1",
diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts
index ffa10cc3c6..cfdfbb0a78 100644
--- a/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts
+++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts
@@ -3,16 +3,24 @@ import {
createRouter,
FilePreparer,
GithubPreparer,
+ GitlabPreparer,
Preparers,
+ Publishers,
GithubPublisher,
+ GitlabPublisher,
CreateReactAppTemplater,
Templaters,
+ RepoVisibilityOptions,
} from '@backstage/plugin-scaffolder-backend';
import { Octokit } from '@octokit/rest';
+import { Gitlab } from '@gitbeaker/node';
import type { PluginEnvironment } from '../types';
import Docker from 'dockerode';
-export default async function createPlugin({ logger }: PluginEnvironment) {
+export default async function createPlugin({
+ logger,
+ config,
+}: PluginEnvironment) {
const cookiecutterTemplater = new CookieCutter();
const craTemplater = new CreateReactAppTemplater();
const templaters = new Templaters();
@@ -21,19 +29,48 @@ export default async function createPlugin({ logger }: PluginEnvironment) {
const filePreparer = new FilePreparer();
const githubPreparer = new GithubPreparer();
+ const gitlabPreparer = new GitlabPreparer(config);
const preparers = new Preparers();
preparers.register('file', filePreparer);
preparers.register('github', githubPreparer);
+ preparers.register('gitlab', gitlabPreparer);
+ preparers.register('gitlab/api', gitlabPreparer);
- const githubClient = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN });
- const publisher = new GithubPublisher({ client: githubClient });
+ const publishers = new Publishers();
+
+ const githubToken = config.getString('scaffolder.github.token');
+ const repoVisibility = config.getString(
+ 'scaffolder.github.visibility',
+ ) as RepoVisibilityOptions;
+
+ const githubClient = new Octokit({ auth: githubToken });
+ const githubPublisher = new GithubPublisher({
+ client: githubClient,
+ token: githubToken,
+ repoVisibility,
+ });
+ publishers.register('file', githubPublisher);
+ publishers.register('github', githubPublisher);
+
+ const gitLabConfig = config.getOptionalConfig('scaffolder.gitlab.api');
+
+ if (gitLabConfig) {
+ const gitLabToken = gitLabConfig.getString('token');
+ const gitLabClient = new Gitlab({
+ host: gitLabConfig.getOptionalString('baseUrl'),
+ token: gitLabToken,
+ });
+ const gitLabPublisher = new GitlabPublisher(gitLabClient, gitLabToken);
+ publishers.register('gitlab', gitLabPublisher);
+ publishers.register('gitlab/api', gitLabPublisher);
+ }
const dockerClient = new Docker();
return await createRouter({
preparers,
templaters,
- publisher,
+ publishers,
logger,
dockerClient,
});
diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/techdocs.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/techdocs.ts
index dfd9eb5e33..9c7de3512b 100644
--- a/packages/create-app/templates/default-app/packages/backend/src/plugins/techdocs.ts
+++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/techdocs.ts
@@ -15,7 +15,7 @@ export default async function createPlugin({
config,
}: PluginEnvironment) {
const generators = new Generators();
- const techdocsGenerator = new TechdocsGenerator(logger);
+ const techdocsGenerator = new TechdocsGenerator(logger, config);
generators.register('techdocs', techdocsGenerator);
diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json
index fe7272bf04..54880cee21 100644
--- a/packages/dev-utils/package.json
+++ b/packages/dev-utils/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/dev-utils",
"description": "Utilities for developing Backstage plugins.",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
@@ -29,10 +29,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/cli": "^0.1.1-alpha.21",
- "@backstage/core": "^0.1.1-alpha.21",
- "@backstage/test-utils": "^0.1.1-alpha.21",
- "@backstage/theme": "^0.1.1-alpha.21",
+ "@backstage/cli": "^0.1.1-alpha.23",
+ "@backstage/core": "^0.1.1-alpha.23",
+ "@backstage/test-utils": "^0.1.1-alpha.23",
+ "@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@testing-library/jest-dom": "^5.10.1",
diff --git a/packages/docgen/package.json b/packages/docgen/package.json
index c75ea53faf..a60a4d1d70 100644
--- a/packages/docgen/package.json
+++ b/packages/docgen/package.json
@@ -1,7 +1,7 @@
{
"name": "docgen",
"description": "Tool for generating API Documentation for itself",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"private": true,
"homepage": "https://backstage.io",
"repository": {
diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json
index 0af233ec8e..fdd8d089c7 100644
--- a/packages/e2e-test/package.json
+++ b/packages/e2e-test/package.json
@@ -1,7 +1,7 @@
{
"name": "e2e-test",
"description": "E2E test for verifying Backstage packages",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"private": true,
"homepage": "https://backstage.io",
"repository": {
@@ -21,7 +21,7 @@
"test:e2e": "yarn start"
},
"devDependencies": {
- "@backstage/cli-common": "^0.1.1-alpha.21",
+ "@backstage/cli-common": "^0.1.1-alpha.23",
"@types/fs-extra": "^9.0.1",
"@types/node": "^13.7.2",
"fs-extra": "^9.0.0",
diff --git a/packages/e2e-test/src/e2e-test.ts b/packages/e2e-test/src/e2e-test.ts
index 5dfac68d6a..969a77cb2a 100644
--- a/packages/e2e-test/src/e2e-test.ts
+++ b/packages/e2e-test/src/e2e-test.ts
@@ -81,7 +81,11 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) {
const path = paths.resolveOwnRoot(pkgJsonPath);
const pkgTemplate = await fs.readFile(path, 'utf8');
const { dependencies = {}, devDependencies = {} } = JSON.parse(
- handlebars.compile(pkgTemplate)({ version: '0.0.0' }),
+ handlebars.compile(pkgTemplate)({
+ version: '0.0.0',
+ privatePackage: true,
+ scopeName: '@backstage',
+ }),
);
Array()
@@ -272,24 +276,37 @@ async function createPlugin(pluginName: string, appDir: string) {
async function testAppServe(pluginName: string, appDir: string) {
const startApp = spawnPiped(['yarn', 'start'], {
cwd: appDir,
+ env: {
+ ...process.env,
+ GITHUB_ACCESS_TOKEN: 'abc',
+ },
});
Browser.localhost('localhost', 3000);
let successful = false;
try {
- const browser = new Browser();
+ for (let attempts = 1; ; attempts++) {
+ try {
+ const browser = new Browser();
- await waitForPageWithText(browser, '/', 'Backstage Service Catalog');
- await waitForPageWithText(
- browser,
- `/${pluginName}`,
- `Welcome to ${pluginName}!`,
- );
+ await waitForPageWithText(browser, '/', 'Backstage Service Catalog');
+ await waitForPageWithText(
+ browser,
+ `/${pluginName}`,
+ `Welcome to ${pluginName}!`,
+ );
- print('Both App and Plugin loaded correctly');
- successful = true;
- } catch (error) {
- throw new Error(`App serve test failed, ${error}`);
+ print('Both App and Plugin loaded correctly');
+ successful = true;
+ break;
+ } catch (error) {
+ if (attempts >= 5) {
+ throw new Error(`App serve test failed, ${error}`);
+ }
+ console.log(`App serve failed, trying again, ${error}`);
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ }
+ }
} finally {
// Kill entire process group, otherwise we'll end up with hanging serve processes
killTree(startApp.pid);
@@ -342,6 +359,10 @@ async function testBackendStart(appDir: string, isPostgres: boolean) {
const child = spawnPiped(['yarn', 'workspace', 'backend', 'start'], {
cwd: appDir,
+ env: {
+ ...process.env,
+ GITHUB_ACCESS_TOKEN: 'abc',
+ },
});
let stdout = '';
@@ -386,5 +407,15 @@ async function testBackendStart(appDir: string, isPostgres: boolean) {
}
}
-process.on('unhandledRejection', handleError);
+process.on('unhandledRejection', (error: Error) => {
+ // Try to avoid exiting if the unhandled error is coming from jsdom, i.e. zombie.
+ // Those are typically errors on the page that should be benign, at least in the
+ // context of this test. We have other ways of asserting that the page is being
+ // rendered correctly.
+ if (error?.stack?.includes('node_modules/jsdom/lib')) {
+ console.log(`Ignored error inside jsdom, ${error}`);
+ } else {
+ handleError(error);
+ }
+});
main().catch(handleError);
diff --git a/packages/storybook/.storybook/apis.js b/packages/storybook/.storybook/apis.js
index a0a220d3de..878256ff5c 100644
--- a/packages/storybook/.storybook/apis.js
+++ b/packages/storybook/.storybook/apis.js
@@ -36,7 +36,7 @@ builder.add(identityApiRef, {
getUserId: () => 'guest',
getProfile: () => ({ email: 'guest@example.com' }),
getIdToken: () => undefined,
- logout: async () => {},
+ signOut: async () => {},
});
const oauthRequestApi = builder.add(
diff --git a/packages/storybook/.storybook/main.js b/packages/storybook/.storybook/main.js
index 2ee58a8c4f..c83bb221be 100644
--- a/packages/storybook/.storybook/main.js
+++ b/packages/storybook/.storybook/main.js
@@ -22,7 +22,7 @@ module.exports = {
const [jsLoader] = config.module.rules.splice(0, 1);
if (jsLoader.use[0].loader !== 'babel-loader') {
throw new Error(
- `Unexpected loader removed from storybook config, ${jsonLoader.use[0].loader}`,
+ `Unexpected loader removed from storybook config, ${jsLoader.use[0].loader}`,
);
}
diff --git a/packages/storybook/package.json b/packages/storybook/package.json
index 8f12a2891c..87d41d47d1 100644
--- a/packages/storybook/package.json
+++ b/packages/storybook/package.json
@@ -1,6 +1,6 @@
{
"name": "storybook",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"description": "Storybook build for core package",
"private": true,
"scripts": {
@@ -14,7 +14,7 @@
]
},
"dependencies": {
- "@backstage/theme": "^0.1.1-alpha.21"
+ "@backstage/theme": "^0.1.1-alpha.23"
},
"devDependencies": {
"@storybook/addon-actions": "^6.0.21",
diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json
index 222620f976..6b31b4aae7 100644
--- a/packages/techdocs-cli/package.json
+++ b/packages/techdocs-cli/package.json
@@ -1,7 +1,7 @@
{
"name": "@techdocs/cli",
"description": "CLI for running TechDocs locally.",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public"
@@ -44,7 +44,7 @@
"ext": "ts"
},
"dependencies": {
- "@backstage/cli": "^0.1.1-alpha.21",
+ "@backstage/cli": "^0.1.1-alpha.23",
"commander": "^6.1.0",
"fs-extra": "^9.0.1",
"http-proxy": "^1.18.1",
diff --git a/packages/techdocs-container/Dockerfile b/packages/techdocs-container/Dockerfile
index 2002d93d70..b34fcbd861 100644
--- a/packages/techdocs-container/Dockerfile
+++ b/packages/techdocs-container/Dockerfile
@@ -18,7 +18,7 @@ FROM python:3.8-alpine
RUN apk update && apk --no-cache add gcc musl-dev openjdk11-jdk curl graphviz ttf-dejavu fontconfig
RUN curl -L http://sourceforge.net/projects/plantuml/files/plantuml.1.2020.16.jar/download > /opt/plantuml.jar
-RUN pip install --upgrade pip && pip install mkdocs-techdocs-core==0.0.4
+RUN pip install --upgrade pip && pip install mkdocs-techdocs-core==0.0.8
# Create script to call plantuml.jar from a location in path
diff --git a/packages/techdocs-container/mock-docs/docs/index.md b/packages/techdocs-container/mock-docs/docs/index.md
index 77c64582de..3c323d6fc1 100644
--- a/packages/techdocs-container/mock-docs/docs/index.md
+++ b/packages/techdocs-container/mock-docs/docs/index.md
@@ -3,10 +3,9 @@
!!! test
Testing somethin
+Abbreviations:
Some text about MOCDOC
-\*[MOCDOC]: Mock Documentation
-
This is a paragraph.
{: #test_id .test_class }
@@ -59,3 +58,42 @@ digraph G {
Goofy <-- MickeyMouse: responds
@enduml
```
+
+:bulb:
+
+=== "JavaScript"
+
+ ```javascript
+ import { test } from 'something';
+
+ const addThingToThing = (a, b) a + b;
+ ```
+
+=== "Java"
+
+ ```java
+ public void function() {
+ test();
+ }
+ ```
+
+```java tab="java"
+ public void function() {
+ test();
+ }
+```
+
+```java tab="java 2"
+ public void function() {
+ test();
+ }
+```
+
+```javascript
+import { test } from 'something';
+
+const addThingToThing = (a, b) a + b;
+```
+
+
+*[MOCDOC]: Mock Documentation
diff --git a/packages/techdocs-container/techdocs-core/README.md b/packages/techdocs-container/techdocs-core/README.md
index a4afed0158..49b9fce4e4 100644
--- a/packages/techdocs-container/techdocs-core/README.md
+++ b/packages/techdocs-container/techdocs-core/README.md
@@ -48,8 +48,84 @@ python -m black src/
**Note:** This will write to all Python files in `src/` with the formatted code. If you would like to only check to see if it passes, simply append the `--check` flag.
+## MkDocs plugins and extensions
+
+The TechDocs Core MkDocs plugin comes with a set of extensions and plugins that mkdocs supports. Below you can find a list of all extensions and plugins that are included in the
+TechDocs Core plugin:
+
+Plugins:
+
+- [search](https://www.mkdocs.org/user-guide/configuration/#search)
+- [mkdocs-monorepo-plugin](https://github.com/spotify/mkdocs-monorepo-plugin)
+
+Extensions:
+
+- [admonition](https://squidfunk.github.io/mkdocs-material/reference/admonitions/#admonitions)
+- [toc](https://python-markdown.github.io/extensions/toc/)
+- [pymdown](https://facelessuser.github.io/pymdown-extensions/)
+ - caret
+ - critic
+ - details
+ - emoji
+ - superfences
+ - inlinehilite
+ - magiclink
+ - mark
+ - smartsymobls
+ - highlight
+ - extra
+ - tabbed
+ - tasklist
+ - tilde
+- [markdown_inline_graphviz](https://pypi.org/project/markdown-inline-graphviz/)
+- [plantuml_markdown](https://pypi.org/project/plantuml-markdown/)
+
## Changelog
+### 0.0.8
+
+- Superfences and Codehilite doesn't work very well together (squidfunk/mkdocs-material#1604) so therefore the codehilite extension is replaced by pymdownx.highlight
+
+* Uses pymdownx extensions v.7.1 instead of 8.0.0 to allow legacy_tab_classes config. This makes the techdocs core plugin compatible with the usage of tabs for grouping markdown with the following syntax:
+
+````
+ ```java tab="java 2"
+ public void function() {
+ ....
+ }
+ ```
+````
+
+as well as the new
+
+````
+ === "Java"
+
+ ```java
+ public void function() {
+ ....
+ }
+ ```
+````
+
+The pymdownx extension will be bumped too 8.0.0 in the near future.
+
+- pymdownx.tabbed is added to support tabs to group markdown content, such as codeblocks.
+
+- "PyMdown Extensions includes three extensions that are meant to replace their counterpart in the default Python Markdown extensions." Therefore some extensions has been taken away in this version that comes by default from pymdownx.extra which is added now (https://facelessuser.github.io/pymdown-extensions/usage_notes/#incompatible-extensions)
+
+### 0.0.7
+
+- Fix an issue with configuration of emoji support
+
+### 0.0.6
+
+- Further adjustments to versions to find ones that are compatible
+
+### 0.0.5
+
+- Downgrade some versions of markdown extensions to versions that are more stable
+
### 0.0.4
- Added support for more mkdocs extensions
diff --git a/packages/techdocs-container/techdocs-core/requirements.txt b/packages/techdocs-container/techdocs-core/requirements.txt
index 4037a174ea..75546fa992 100644
--- a/packages/techdocs-container/techdocs-core/requirements.txt
+++ b/packages/techdocs-container/techdocs-core/requirements.txt
@@ -4,7 +4,7 @@
mkdocs==1.1.2
mkdocs-material==5.3.2
mkdocs-monorepo-plugin==0.4.5
-plantuml-markdown==3.4.0
+plantuml-markdown==3.1.2
markdown_inline_graphviz_extension==1.1
pygments==2.6.1
pymdown-extensions==7.1
diff --git a/packages/techdocs-container/techdocs-core/setup.py b/packages/techdocs-container/techdocs-core/setup.py
index 72593dc433..e5a8206150 100644
--- a/packages/techdocs-container/techdocs-core/setup.py
+++ b/packages/techdocs-container/techdocs-core/setup.py
@@ -17,38 +17,34 @@ from setuptools import setup, find_packages
setup(
- name='mkdocs-techdocs-core',
- version='0.0.4',
- description='A Mkdocs package that contains TechDocs defaults',
- long_description='',
- keywords='mkdocs',
- url='https://github.com/spotify/backstage',
- author='TechDocs Core',
- author_email='pulp-fiction@spotify.com',
- license='Apache-2.0',
- python_requires='>=3.7',
+ name="mkdocs-techdocs-core",
+ version="0.0.8",
+ description="A Mkdocs package that contains TechDocs defaults",
+ long_description="",
+ keywords="mkdocs",
+ url="https://github.com/spotify/backstage",
+ author="TechDocs Core",
+ author_email="pulp-fiction@spotify.com",
+ license="Apache-2.0",
+ python_requires=">=3.7",
install_requires=[
- 'mkdocs>=1.1.2',
- 'mkdocs-material==5.3.2',
- 'mkdocs-monorepo-plugin==0.4.5',
- 'plantuml-markdown==3.4.0',
- 'markdown_inline_graphviz_extension==1.1',
- 'pygments==2.6.1',
- 'pymdown-extensions==7.1'
+ "mkdocs>=1.1.2",
+ "mkdocs-material==5.3.2",
+ "mkdocs-monorepo-plugin==0.4.5",
+ "plantuml-markdown==3.1.2",
+ "markdown_inline_graphviz_extension==1.1",
+ "pygments==2.6.1",
+ "pymdown-extensions==7.1",
],
classifiers=[
- 'Development Status :: 1 - Planning',
- 'Intended Audience :: Developers',
- 'Intended Audience :: Information Technology',
- 'License :: OSI Approved :: Apache Software License',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 3 :: Only',
- 'Programming Language :: Python :: 3.7'
+ "Development Status :: 1 - Planning",
+ "Intended Audience :: Developers",
+ "Intended Audience :: Information Technology",
+ "License :: OSI Approved :: Apache Software License",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.7",
],
packages=find_packages(),
- entry_points={
- 'mkdocs.plugins': [
- 'techdocs-core = src.core:TechDocsCore'
- ]
- }
+ entry_points={"mkdocs.plugins": ["techdocs-core = src.core:TechDocsCore"]},
)
diff --git a/packages/techdocs-container/techdocs-core/src/core.py b/packages/techdocs-container/techdocs-core/src/core.py
index d021a69486..ed1eb7c6f6 100644
--- a/packages/techdocs-container/techdocs-core/src/core.py
+++ b/packages/techdocs-container/techdocs-core/src/core.py
@@ -14,10 +14,11 @@
* limitations under the License.
"""
-from mkdocs.plugins import BasePlugin, PluginCollection
+from mkdocs.plugins import BasePlugin
from mkdocs.theme import Theme
from mkdocs.contrib.search import SearchPlugin
from mkdocs_monorepo_plugin.plugin import MonorepoPlugin
+from pymdownx.emoji import to_svg
import tempfile
import os
@@ -53,37 +54,33 @@ class TechDocsCore(BasePlugin):
# Markdown Extensions
config["markdown_extensions"].append("admonition")
- config["markdown_extensions"].append("abbr")
- config["markdown_extensions"].append("attr_list")
- config["markdown_extensions"].append("def_list")
- config["markdown_extensions"].append("codehilite")
- config["mdx_configs"]["codehilite"] = {
- "linenums": True,
- "guess_lang": False,
- "pygments_style": "friendly",
- }
config["markdown_extensions"].append("toc")
config["mdx_configs"]["toc"] = {
"permalink": True,
}
- config["markdown_extensions"].append("footnotes")
- config["markdown_extensions"].append("markdown.extensions.tables")
- config["markdown_extensions"].append("pymdownx.betterem")
- config["mdx_configs"]["pymdownx.betterem"] = {
- "smart_enable": "all",
- }
+
config["markdown_extensions"].append("pymdownx.caret")
config["markdown_extensions"].append("pymdownx.critic")
config["markdown_extensions"].append("pymdownx.details")
config["markdown_extensions"].append("pymdownx.emoji")
- config["mdx_configs"]["pymdownx.emoji"] = {
- "emoji_generator": "!!python/name:pymdownx.emoji.to_svg",
- }
+ config["mdx_configs"]["pymdownx.emoji"] = {"emoji_generator": to_svg}
config["markdown_extensions"].append("pymdownx.inlinehilite")
config["markdown_extensions"].append("pymdownx.magiclink")
config["markdown_extensions"].append("pymdownx.mark")
config["markdown_extensions"].append("pymdownx.smartsymbols")
config["markdown_extensions"].append("pymdownx.superfences")
+ config["mdx_configs"]["pymdownx.superfences"] = {
+ "legacy_tab_classes": True,
+ }
+ config["markdown_extensions"].append("pymdownx.highlight")
+ config["mdx_configs"]["pymdownx.highlight"] = {
+ "linenums": True,
+ }
+ config["markdown_extensions"].append("pymdownx.extra")
+ config["mdx_configs"]["pymdownx.betterem"] = {
+ "smart_enable": "all",
+ }
+ config["markdown_extensions"].append("pymdownx.tabbed")
config["markdown_extensions"].append("pymdownx.tasklist")
config["mdx_configs"]["pymdownx.tasklist"] = {
"custom_checkbox": True,
diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json
index 8439bee779..e30a1e9409 100644
--- a/packages/test-utils-core/package.json
+++ b/packages/test-utils-core/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/test-utils-core",
"description": "Utilities to test Backstage core",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json
index 8567af8c14..2ca946e028 100644
--- a/packages/test-utils/package.json
+++ b/packages/test-utils/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/test-utils",
"description": "Utilities to test Backstage plugins and apps.",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
@@ -29,10 +29,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/cli": "^0.1.1-alpha.21",
- "@backstage/core-api": "^0.1.1-alpha.21",
- "@backstage/test-utils-core": "^0.1.1-alpha.21",
- "@backstage/theme": "^0.1.1-alpha.21",
+ "@backstage/cli": "^0.1.1-alpha.23",
+ "@backstage/core-api": "^0.1.1-alpha.23",
+ "@backstage/test-utils-core": "^0.1.1-alpha.23",
+ "@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
diff --git a/packages/theme/package.json b/packages/theme/package.json
index 436455633b..bd8705933f 100644
--- a/packages/theme/package.json
+++ b/packages/theme/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/theme",
"description": "material-ui theme for use with Backstage.",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
@@ -31,7 +31,7 @@
"@material-ui/core": "^4.11.0"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.21"
+ "@backstage/cli": "^0.1.1-alpha.23"
},
"files": [
"dist"
diff --git a/plugins/api-docs/README.md b/plugins/api-docs/README.md
index db78b60373..17f354e997 100644
--- a/plugins/api-docs/README.md
+++ b/plugins/api-docs/README.md
@@ -14,8 +14,9 @@ The plugin provides a standalone list of APIs, as well as an integration into th
Right now, the following API formats are supported:
-- [OpenAPI](https://swagger.io/specification/) 2 & 3,
-- [AsyncAPI](https://www.asyncapi.com/docs/specifications/latest/),
+- [OpenAPI](https://swagger.io/specification/) 2 & 3
+- [AsyncAPI](https://www.asyncapi.com/docs/specifications/latest/)
+- [GraphQL](https://graphql.org/learn/schema/)
Other formats are displayed as plain text, but this can easily be extented.
diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json
index a913316925..bc384bf796 100644
--- a/plugins/api-docs/package.json
+++ b/plugins/api-docs/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-api-docs",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,15 +20,17 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.1.1-alpha.21",
- "@backstage/core": "^0.1.1-alpha.21",
- "@backstage/plugin-catalog": "^0.1.1-alpha.21",
- "@backstage/theme": "^0.1.1-alpha.21",
+ "@backstage/catalog-model": "^0.1.1-alpha.23",
+ "@backstage/core": "^0.1.1-alpha.23",
+ "@backstage/plugin-catalog": "^0.1.1-alpha.23",
+ "@backstage/theme": "^0.1.1-alpha.23",
"@kyma-project/asyncapi-react": "^0.11.0",
"@material-icons/font": "^1.0.2",
"@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",
+ "graphql": "^15.3.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router": "6.0.0-beta.0",
@@ -37,9 +39,9 @@
"swagger-ui-react": "^3.31.1"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.21",
- "@backstage/dev-utils": "^0.1.1-alpha.21",
- "@backstage/test-utils": "^0.1.1-alpha.21",
+ "@backstage/cli": "^0.1.1-alpha.23",
+ "@backstage/dev-utils": "^0.1.1-alpha.23",
+ "@backstage/test-utils": "^0.1.1-alpha.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
diff --git a/plugins/api-docs/src/catalog/EntityPageApi/EntityPageApi.tsx b/plugins/api-docs/src/catalog/EntityPageApi/EntityPageApi.tsx
index da2cf71ac4..e2148184b7 100644
--- a/plugins/api-docs/src/catalog/EntityPageApi/EntityPageApi.tsx
+++ b/plugins/api-docs/src/catalog/EntityPageApi/EntityPageApi.tsx
@@ -16,15 +16,19 @@
import { ComponentEntity, Entity } from '@backstage/catalog-model';
import { Progress } from '@backstage/core';
-import React, { FC } from 'react';
import { Grid } from '@material-ui/core';
+import React from 'react';
import {
ApiDefinitionCard,
useComponentApiEntities,
useComponentApiNames,
} from '../../components';
-export const EntityPageApi: FC<{ entity: Entity }> = ({ entity }) => {
+type Props = {
+ entity: Entity;
+};
+
+export const EntityPageApi = ({ entity }: Props) => {
const apiNames = useComponentApiNames(entity as ComponentEntity);
const { apiEntities, loading } = useComponentApiEntities({
@@ -39,7 +43,7 @@ export const EntityPageApi: FC<{ entity: Entity }> = ({ entity }) => {
{apiNames.map(api => (
-
+
))}
diff --git a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx
index c4a366c4d9..49b8a69318 100644
--- a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx
+++ b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx
@@ -15,31 +15,93 @@
*/
import { ApiEntity } from '@backstage/catalog-model';
-import { InfoCard } from '@backstage/core';
-import React from 'react';
-import { ApiDefinitionWidget } from '../ApiDefinitionWidget';
+import { CardTab, useApi, TabbedCard } from '@backstage/core';
import { Alert } from '@material-ui/lab';
+import React from 'react';
+import { apiDocsConfigRef } from '../../config';
+import { PlainApiDefinitionWidget } from '../PlainApiDefinitionWidget';
+import { OpenApiDefinitionWidget } from '../OpenApiDefinitionWidget';
+import { AsyncApiDefinitionWidget } from '../AsyncApiDefinitionWidget';
+import { GraphQlDefinitionWidget } from '../GraphQlDefinitionWidget';
+
+export type ApiDefinitionWidget = {
+ type: string;
+ title: string;
+ component: (definition: string) => React.ReactElement;
+ rawLanguage?: string;
+};
+
+export function defaultDefinitionWidgets(): ApiDefinitionWidget[] {
+ return [
+ {
+ type: 'openapi',
+ title: 'OpenAPI',
+ rawLanguage: 'yaml',
+ component: definition => (
+
+ ),
+ },
+ {
+ type: 'asyncapi',
+ title: 'AsyncAPI',
+ rawLanguage: 'yaml',
+ component: definition => (
+
+ ),
+ },
+ {
+ type: 'graphql',
+ title: 'GraphQL',
+ rawLanguage: 'graphql',
+ component: definition => (
+
+ ),
+ },
+ ];
+}
type Props = {
- title?: string;
apiEntity?: ApiEntity;
};
-export const ApiDefinitionCard = ({ title, apiEntity }: Props) => {
+export const ApiDefinitionCard = ({ apiEntity }: Props) => {
+ const config = useApi(apiDocsConfigRef);
+ const { getApiDefinitionWidget } = config;
+
if (!apiEntity) {
+ return Could not fetch the API ;
+ }
+
+ const definitionWidget = getApiDefinitionWidget(apiEntity);
+
+ if (definitionWidget) {
return (
-
- Could not fetch the API
-
+
+
+ {definitionWidget.component(apiEntity.spec.definition)}
+
+
+
+
+
);
}
return (
-
-
-
+
+
+ ,
+ ]}
+ />
);
};
diff --git a/plugins/api-docs/src/components/ApiDefinitionCard/index.ts b/plugins/api-docs/src/components/ApiDefinitionCard/index.ts
index b2a2f3af62..de7856f70c 100644
--- a/plugins/api-docs/src/components/ApiDefinitionCard/index.ts
+++ b/plugins/api-docs/src/components/ApiDefinitionCard/index.ts
@@ -14,4 +14,8 @@
* limitations under the License.
*/
-export { ApiDefinitionCard } from './ApiDefinitionCard';
+export type { ApiDefinitionWidget } from './ApiDefinitionCard';
+export {
+ ApiDefinitionCard,
+ defaultDefinitionWidgets,
+} from './ApiDefinitionCard';
diff --git a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx
index aa0df3f20f..b7e9996cc0 100644
--- a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx
+++ b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx
@@ -34,6 +34,7 @@ import { useAsync } from 'react-use';
import { ApiDefinitionCard } from '../ApiDefinitionCard';
const REDIRECT_DELAY = 1000;
+
function headerProps(
kind: string,
namespace: string | undefined,
diff --git a/plugins/api-docs/src/components/ApiCatalogPage/ApiCatalogLayout.tsx b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx
similarity index 84%
rename from plugins/api-docs/src/components/ApiCatalogPage/ApiCatalogLayout.tsx
rename to plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx
index d846e2b0e2..a111007278 100644
--- a/plugins/api-docs/src/components/ApiCatalogPage/ApiCatalogLayout.tsx
+++ b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx
@@ -21,17 +21,15 @@ type Props = {
children?: React.ReactNode;
};
-const ApiCatalogLayout = ({ children }: Props) => {
+export const ApiExplorerLayout = ({ children }: Props) => {
return (
{children}
);
};
-
-export default ApiCatalogLayout;
diff --git a/plugins/api-docs/src/components/ApiCatalogPage/ApiCatalogPage.test.tsx b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.test.tsx
similarity index 85%
rename from plugins/api-docs/src/components/ApiCatalogPage/ApiCatalogPage.test.tsx
rename to plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.test.tsx
index 78afa51937..e386f281e8 100644
--- a/plugins/api-docs/src/components/ApiCatalogPage/ApiCatalogPage.test.tsx
+++ b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.test.tsx
@@ -20,7 +20,8 @@ import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog';
import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils';
import { render } from '@testing-library/react';
import React from 'react';
-import { ApiCatalogPage } from './ApiCatalogPage';
+import { apiDocsConfigRef } from '../../config';
+import { ApiExplorerPage } from './ApiExplorerPage';
describe('ApiCatalogPage', () => {
const catalogApi: Partial = {
@@ -32,6 +33,7 @@ describe('ApiCatalogPage', () => {
metadata: {
name: 'Entity1',
},
+ spec: { type: 'openapi' },
},
{
apiVersion: 'backstage.io/v1alpha1',
@@ -39,12 +41,17 @@ describe('ApiCatalogPage', () => {
metadata: {
name: 'Entity2',
},
+ spec: { type: 'openapi' },
},
] as Entity[]),
getLocationByEntity: () =>
Promise.resolve({ id: 'id', type: 'github', target: 'url' }),
};
+ const apiDocsConfig = {
+ getApiDefinitionWidget: () => undefined,
+ };
+
const renderWrapped = (children: React.ReactNode) =>
render(
wrapInTestApp(
@@ -52,6 +59,7 @@ describe('ApiCatalogPage', () => {
apis={ApiRegistry.from([
[catalogApiRef, catalogApi],
[storageApiRef, MockStorageApi.create()],
+ [apiDocsConfigRef, apiDocsConfig],
])}
>
{children}
@@ -63,7 +71,7 @@ describe('ApiCatalogPage', () => {
// related to some theme issues in mui-table
// https://github.com/mbrn/material-table/issues/1293
it('should render', async () => {
- const { findByText } = renderWrapped( );
+ const { findByText } = renderWrapped( );
expect(await findByText(/APIs \(2\)/)).toBeInTheDocument();
});
});
diff --git a/plugins/api-docs/src/components/ApiCatalogPage/ApiCatalogPage.tsx b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.tsx
similarity index 59%
rename from plugins/api-docs/src/components/ApiCatalogPage/ApiCatalogPage.tsx
rename to plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.tsx
index cfeb71f7b7..b6440edc3a 100644
--- a/plugins/api-docs/src/components/ApiCatalogPage/ApiCatalogPage.tsx
+++ b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.tsx
@@ -14,31 +14,42 @@
* limitations under the License.
*/
-import { Content, useApi } from '@backstage/core';
+import { Content, ContentHeader, SupportButton, useApi } from '@backstage/core';
import { catalogApiRef } from '@backstage/plugin-catalog';
+import { Button } from '@material-ui/core';
import React from 'react';
+import { Link as RouterLink } from 'react-router-dom';
import { useAsync } from 'react-use';
-import { ApiCatalogTable } from '../ApiCatalogTable';
-import ApiCatalogLayout from './ApiCatalogLayout';
+import { ApiExplorerTable } from '../ApiExplorerTable';
+import { ApiExplorerLayout } from './ApiExplorerLayout';
-const CatalogPageContents = () => {
+export const ApiExplorerPage = () => {
const catalogApi = useApi(catalogApiRef);
const { loading, error, value: matchingEntities } = useAsync(() => {
return catalogApi.getEntities({ kind: 'API' });
}, [catalogApi]);
return (
-
+
-
+
+ Register Existing API
+
+ All your APIs
+
+
-
+
);
};
-
-export const ApiCatalogPage = () => ;
diff --git a/plugins/api-docs/src/components/ApiCatalogTable/index.ts b/plugins/api-docs/src/components/ApiExplorerPage/index.ts
similarity index 91%
rename from plugins/api-docs/src/components/ApiCatalogTable/index.ts
rename to plugins/api-docs/src/components/ApiExplorerPage/index.ts
index 14129b2258..67f672f9a9 100644
--- a/plugins/api-docs/src/components/ApiCatalogTable/index.ts
+++ b/plugins/api-docs/src/components/ApiExplorerPage/index.ts
@@ -14,4 +14,4 @@
* limitations under the License.
*/
-export { ApiCatalogTable } from './ApiCatalogTable';
+export { ApiExplorerPage } from './ApiExplorerPage';
diff --git a/plugins/api-docs/src/components/ApiCatalogTable/ApiCatalogTable.test.tsx b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.test.tsx
similarity index 70%
rename from plugins/api-docs/src/components/ApiCatalogTable/ApiCatalogTable.test.tsx
rename to plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.test.tsx
index 67f6562a56..c679c2201f 100644
--- a/plugins/api-docs/src/components/ApiCatalogTable/ApiCatalogTable.test.tsx
+++ b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.test.tsx
@@ -15,39 +15,50 @@
*/
import { Entity } from '@backstage/catalog-model';
+import { ApiProvider, ApiRegistry } from '@backstage/core';
import { wrapInTestApp } from '@backstage/test-utils';
import { render } from '@testing-library/react';
import * as React from 'react';
-import { ApiCatalogTable } from './ApiCatalogTable';
+import { apiDocsConfigRef } from '../../config';
+import { ApiExplorerTable } from './ApiExplorerTable';
const entites: Entity[] = [
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'API',
metadata: { name: 'api1' },
+ spec: { type: 'openapi' },
},
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'API',
metadata: { name: 'api2' },
+ spec: { type: 'openapi' },
},
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'API',
metadata: { name: 'api3' },
+ spec: { type: 'grpc' },
},
];
+const apiRegistry = ApiRegistry.with(apiDocsConfigRef, {
+ getApiDefinitionWidget: () => undefined,
+});
+
describe('ApiCatalogTable component', () => {
it('should render error message when error is passed in props', async () => {
const rendered = render(
wrapInTestApp(
- ,
+
+
+ ,
),
);
const errorMessage = await rendered.findByText(
@@ -59,11 +70,13 @@ describe('ApiCatalogTable component', () => {
it('should display entity names when loading has finished and no error occurred', async () => {
const rendered = render(
wrapInTestApp(
- ,
+
+
+ ,
),
);
expect(rendered.getByText(/APIs \(3\)/)).toBeInTheDocument();
diff --git a/plugins/api-docs/src/components/ApiCatalogTable/ApiCatalogTable.tsx b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx
similarity index 63%
rename from plugins/api-docs/src/components/ApiCatalogTable/ApiCatalogTable.tsx
rename to plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx
index 3b954b5a86..485fcc216d 100644
--- a/plugins/api-docs/src/components/ApiCatalogTable/ApiCatalogTable.tsx
+++ b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx
@@ -14,14 +14,23 @@
* limitations under the License.
*/
-import { Entity } from '@backstage/catalog-model';
-import { Table, TableColumn } from '@backstage/core';
-import { Link } from '@material-ui/core';
+import { ApiEntityV1alpha1, Entity } from '@backstage/catalog-model';
+import { Table, TableColumn, useApi } from '@backstage/core';
+import { Chip, Link } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import React from 'react';
import { generatePath, Link as RouterLink } from 'react-router-dom';
+import { apiDocsConfigRef } from '../../config';
import { entityRoute } from '../../routes';
+const ApiTypeTitle = ({ apiEntity }: { apiEntity: ApiEntityV1alpha1 }) => {
+ const config = useApi(apiDocsConfigRef);
+ const definition = config.getApiDefinitionWidget(apiEntity);
+ const type = definition ? definition.title : apiEntity.spec.type;
+
+ return {type} ;
+};
+
const columns: TableColumn[] = [
{
title: 'Name',
@@ -45,25 +54,55 @@ const columns: TableColumn[] = [
),
},
+ {
+ title: 'Owner',
+ field: 'spec.owner',
+ },
+ {
+ title: 'Lifecycle',
+ field: 'spec.lifecycle',
+ },
+ {
+ title: 'Type',
+ field: 'spec.type',
+ render: (entity: Entity) => (
+
+ ),
+ },
{
title: 'Description',
field: 'metadata.description',
},
+ {
+ title: 'Tags',
+ field: 'metadata.tags',
+ cellStyle: {
+ padding: '0px 16px 0px 20px',
+ },
+ render: (entity: Entity) => (
+ <>
+ {entity.metadata.tags &&
+ entity.metadata.tags.map(t => (
+
+ ))}
+ >
+ ),
+ },
];
-type CatalogTableProps = {
+type ExplorerTableProps = {
entities: Entity[];
titlePreamble: string;
loading: boolean;
error?: any;
};
-export const ApiCatalogTable = ({
+export const ApiExplorerTable = ({
entities,
loading,
error,
titlePreamble,
-}: CatalogTableProps) => {
+}: ExplorerTableProps) => {
if (error) {
return (
diff --git a/plugins/api-docs/src/components/ApiExplorerTable/index.ts b/plugins/api-docs/src/components/ApiExplorerTable/index.ts
new file mode 100644
index 0000000000..a9c79861e8
--- /dev/null
+++ b/plugins/api-docs/src/components/ApiExplorerTable/index.ts
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+export { ApiExplorerTable } from './ApiExplorerTable';
diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx
index 2f64c4b2ab..ac5dedfd9c 100644
--- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx
+++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx
@@ -136,7 +136,7 @@ const useStyles = makeStyles(theme => ({
}));
type Props = {
- definition: any;
+ definition: string;
};
export const AsyncApiDefinitionWidget = ({ definition }: Props) => {
diff --git a/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinitionWidget.tsx b/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinitionWidget.tsx
new file mode 100644
index 0000000000..7e01df1460
--- /dev/null
+++ b/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinitionWidget.tsx
@@ -0,0 +1,66 @@
+/*
+ * 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, { Suspense } from 'react';
+import { buildSchema } from 'graphql';
+import { makeStyles } from '@material-ui/core/styles';
+import { Progress } from '@backstage/core';
+import { BackstageTheme } from '@backstage/theme';
+
+const GraphiQL = React.lazy(() => import('graphiql'));
+
+const useStyles = makeStyles
(() => ({
+ root: {
+ height: '100%',
+ display: 'flex',
+ flexFlow: 'column nowrap',
+ },
+ graphiQlWrapper: {
+ flex: 1,
+ '@global': {
+ '.graphiql-container': {
+ boxSizing: 'initial',
+ height: '100%',
+ minHeight: '600px',
+ flex: '1 1 auto',
+ },
+ },
+ },
+}));
+
+type Props = {
+ definition: any;
+};
+
+export const GraphQlDefinitionWidget = ({ definition }: Props) => {
+ const classes = useStyles();
+ const schema = buildSchema(definition);
+
+ return (
+ }>
+
+
+ Promise.resolve(null) as any}
+ schema={schema}
+ docExplorerOpen
+ defaultSecondaryEditorOpen={false}
+ />
+
+
+
+ );
+};
diff --git a/plugins/api-docs/src/components/GraphQlDefinitionWidget/index.ts b/plugins/api-docs/src/components/GraphQlDefinitionWidget/index.ts
new file mode 100644
index 0000000000..b60545de15
--- /dev/null
+++ b/plugins/api-docs/src/components/GraphQlDefinitionWidget/index.ts
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+export { GraphQlDefinitionWidget } from './GraphQlDefinitionWidget';
diff --git a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx
index 61f5cb2b75..b96f50166e 100644
--- a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx
+++ b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx
@@ -66,7 +66,7 @@ const useStyles = makeStyles(theme => ({
}));
type Props = {
- definition: any;
+ definition: string;
};
export const OpenApiDefinitionWidget = ({ definition }: Props) => {
diff --git a/plugins/api-docs/src/components/PlainApiDefinitionWidget/PlainApiDefinitionWidget.tsx b/plugins/api-docs/src/components/PlainApiDefinitionWidget/PlainApiDefinitionWidget.tsx
index 46631c5a4b..0b2ebb4ca4 100644
--- a/plugins/api-docs/src/components/PlainApiDefinitionWidget/PlainApiDefinitionWidget.tsx
+++ b/plugins/api-docs/src/components/PlainApiDefinitionWidget/PlainApiDefinitionWidget.tsx
@@ -23,5 +23,7 @@ type Props = {
};
export const PlainApiDefinitionWidget = ({ definition, language }: Props) => {
- return ;
+ return (
+
+ );
};
diff --git a/plugins/api-docs/src/components/index.ts b/plugins/api-docs/src/components/index.ts
index d49303c24a..cf9b189091 100644
--- a/plugins/api-docs/src/components/index.ts
+++ b/plugins/api-docs/src/components/index.ts
@@ -14,6 +14,13 @@
* limitations under the License.
*/
-export { ApiDefinitionCard } from './ApiDefinitionCard';
+export type { ApiDefinitionWidget } from './ApiDefinitionCard';
+export {
+ ApiDefinitionCard,
+ defaultDefinitionWidgets,
+} from './ApiDefinitionCard';
+export { AsyncApiDefinitionWidget } from './AsyncApiDefinitionWidget';
+export { OpenApiDefinitionWidget } from './OpenApiDefinitionWidget';
+export { PlainApiDefinitionWidget } from './PlainApiDefinitionWidget';
export { useComponentApiNames } from './useComponentApiNames';
export { useComponentApiEntities } from './useComponentApiEntities';
diff --git a/plugins/api-docs/src/config.ts b/plugins/api-docs/src/config.ts
new file mode 100644
index 0000000000..a90189a037
--- /dev/null
+++ b/plugins/api-docs/src/config.ts
@@ -0,0 +1,30 @@
+/*
+ * 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 { ApiEntity } from '@backstage/catalog-model';
+import { createApiRef } from '@backstage/core';
+import { ApiDefinitionWidget } from './components';
+
+export const apiDocsConfigRef = createApiRef({
+ id: 'plugin.api-docs.config',
+ description: 'Used to configure api-docs widgets',
+});
+
+export interface ApiDocsConfig {
+ getApiDefinitionWidget: (
+ apiEntity: ApiEntity,
+ ) => ApiDefinitionWidget | undefined;
+}
diff --git a/plugins/api-docs/src/plugin.ts b/plugins/api-docs/src/plugin.ts
index db58538b67..3fcbe8a1f4 100644
--- a/plugins/api-docs/src/plugin.ts
+++ b/plugins/api-docs/src/plugin.ts
@@ -14,15 +14,32 @@
* limitations under the License.
*/
-import { createPlugin } from '@backstage/core';
-import { ApiCatalogPage } from './components/ApiCatalogPage/ApiCatalogPage';
+import { ApiEntity } from '@backstage/catalog-model';
+import { createApiFactory, createPlugin } from '@backstage/core';
+import { ApiExplorerPage } from './components/ApiExplorerPage/ApiExplorerPage';
+import { defaultDefinitionWidgets } from './components/ApiDefinitionCard';
import { ApiEntityPage } from './components/ApiEntityPage/ApiEntityPage';
import { entityRoute, rootRoute } from './routes';
+import { apiDocsConfigRef } from './config';
export const plugin = createPlugin({
id: 'api-docs',
+ apis: [
+ createApiFactory({
+ api: apiDocsConfigRef,
+ deps: {},
+ factory: () => {
+ const definitionWidgets = defaultDefinitionWidgets();
+ return {
+ getApiDefinitionWidget: (apiEntity: ApiEntity) => {
+ return definitionWidgets.find(d => d.type === apiEntity.spec.type);
+ },
+ };
+ },
+ }),
+ ],
register({ router }) {
- router.addRoute(rootRoute, ApiCatalogPage);
+ router.addRoute(rootRoute, ApiExplorerPage);
router.addRoute(entityRoute, ApiEntityPage);
},
});
diff --git a/plugins/api-docs/src/routes.ts b/plugins/api-docs/src/routes.ts
index eea911dd62..d2d8ef10c5 100644
--- a/plugins/api-docs/src/routes.ts
+++ b/plugins/api-docs/src/routes.ts
@@ -23,11 +23,13 @@ export const rootRoute = createRouteRef({
path: '/api-docs',
title: 'APIs',
});
+
export const entityRoute = createRouteRef({
icon: NoIcon,
path: '/api-docs/:optionalNamespaceAndName/',
title: 'API',
});
+
export const catalogRoute = createRouteRef({
icon: NoIcon,
path: '',
diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json
index f4080abc42..8d7fb67171 100644
--- a/plugins/app-backend/package.json
+++ b/plugins/app-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-app-backend",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,8 +20,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.1.1-alpha.21",
- "@backstage/config-loader": "^0.1.1-alpha.21",
+ "@backstage/backend-common": "^0.1.1-alpha.23",
+ "@backstage/config-loader": "^0.1.1-alpha.23",
"@types/express": "^4.17.6",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
@@ -30,7 +30,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.21",
+ "@backstage/cli": "^0.1.1-alpha.23",
"@types/supertest": "^2.0.8",
"msw": "^0.19.5",
"supertest": "^4.0.2"
diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json
index 2336adb860..d1c563229c 100644
--- a/plugins/auth-backend/package.json
+++ b/plugins/auth-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,8 +20,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.1.1-alpha.21",
- "@backstage/config": "^0.1.1-alpha.21",
+ "@backstage/backend-common": "^0.1.1-alpha.23",
+ "@backstage/config": "^0.1.1-alpha.23",
"@types/express": "^4.17.6",
"compression": "^1.7.4",
"cookie-parser": "^1.4.5",
@@ -49,7 +49,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.21",
+ "@backstage/cli": "^0.1.1-alpha.23",
"@types/body-parser": "^1.19.0",
"@types/cookie-parser": "^1.4.2",
"@types/jwt-decode": "2.2.1",
diff --git a/plugins/catalog-backend/README.md b/plugins/catalog-backend/README.md
index 7c52dfad72..1b4a653f4a 100644
--- a/plugins/catalog-backend/README.md
+++ b/plugins/catalog-backend/README.md
@@ -21,12 +21,9 @@ To evaluate the catalog and have a greater amount of functionality available, in
# in one terminal window, run this from from the very root of the Backstage project
cd packages/backend
yarn start
-
-# open another terminal window, and run the following from the very root of the Backstage project
-yarn lerna run mock-data
```
-This will launch the full example backend and populate its catalog with some mock entities.
+This will launch the full example backend, populated some example entities.
## Links
diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json
index c1b20ed7bb..7e3134a012 100644
--- a/plugins/catalog-backend/package.json
+++ b/plugins/catalog-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend",
- "version": "0.1.1-alpha.21",
+ "version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -17,18 +17,17 @@
"test": "backstage-cli test",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack",
- "clean": "backstage-cli clean",
- "mock-data": "./scripts/mock-data.sh",
- "mock-data:local": "./scripts/mock-data-local.sh"
+ "clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.1.1-alpha.21",
- "@backstage/catalog-model": "^0.1.1-alpha.21",
- "@backstage/config": "^0.1.1-alpha.21",
+ "@backstage/backend-common": "^0.1.1-alpha.23",
+ "@backstage/catalog-model": "^0.1.1-alpha.23",
+ "@backstage/config": "^0.1.1-alpha.23",
"@types/express": "^4.17.6",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
"fs-extra": "^9.0.0",
+ "git-url-parse": "^11.2.0",
"knex": "^0.21.1",
"lodash": "^4.17.15",
"morgan": "^1.10.0",
@@ -41,7 +40,8 @@
"yup": "^0.29.1"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.21",
+ "@backstage/cli": "^0.1.1-alpha.23",
+ "@types/git-url-parse": "^9.0.0",
"@types/lodash": "^4.14.151",
"@types/node-fetch": "^2.5.7",
"@types/supertest": "^2.0.8",
diff --git a/plugins/catalog-backend/scripts/mock-data-local.sh b/plugins/catalog-backend/scripts/mock-data-local.sh
deleted file mode 100755
index 3bea819319..0000000000
--- a/plugins/catalog-backend/scripts/mock-data-local.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env bash
-
-for FILE in \
- ../../packages/catalog-model/examples/*.yaml \
-; do \
- curl \
- --location \
- --request POST 'localhost:7000/catalog/locations' \
- --header 'Content-Type: application/json' \
- --data-raw "{\"type\": \"file\", \"target\": \"../catalog-model/${FILE}\"}"
- echo
-done
diff --git a/plugins/catalog-backend/scripts/mock-data.sh b/plugins/catalog-backend/scripts/mock-data.sh
deleted file mode 100755
index 92ec647281..0000000000
--- a/plugins/catalog-backend/scripts/mock-data.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env bash
-
-for URL in \
- 'artist-lookup-component.yaml' \
- 'playback-order-component.yaml' \
- 'podcast-api-component.yaml' \
- 'queue-proxy-component.yaml' \
- 'searcher-component.yaml' \
- 'playback-lib-component.yaml' \
- 'www-artist-component.yaml' \
- 'shuffle-api-component.yaml' \
- 'petstore-api.yaml' \
- 'streetlights-api.yaml' \
-; do \
- curl \
- --location \
- --request POST 'localhost:7000/catalog/locations' \
- --header 'Content-Type: application/json' \
- --data-raw "{\"type\": \"github\", \"target\": \"https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/${URL}\"}"
- echo
-done
diff --git a/plugins/catalog-backend/src/database/CommonDatabase.ts b/plugins/catalog-backend/src/database/CommonDatabase.ts
index 0c56ca4370..4ad5362fb6 100644
--- a/plugins/catalog-backend/src/database/CommonDatabase.ts
+++ b/plugins/catalog-backend/src/database/CommonDatabase.ts
@@ -343,7 +343,14 @@ export class CommonDatabase implements Database {
entityName?: string,
message?: string,
): Promise {
- return this.database(
+ // Remove log entries older than a day
+ const cutoff = new Date();
+ cutoff.setDate(cutoff.getDate() - 1);
+ await this.database('location_update_log')
+ .where('created_at', '<', cutoff.toISOString())
+ .del();
+
+ await this.database(
'location_update_log',
).insert({
status,
diff --git a/plugins/catalog-backend/src/ingestion/LocationReaders.ts b/plugins/catalog-backend/src/ingestion/LocationReaders.ts
index a86e073af9..8da6e20ab3 100644
--- a/plugins/catalog-backend/src/ingestion/LocationReaders.ts
+++ b/plugins/catalog-backend/src/ingestion/LocationReaders.ts
@@ -27,7 +27,6 @@ import { AnnotateLocationEntityProcessor } from './processors/AnnotateLocationEn
import { EntityPolicyProcessor } from './processors/EntityPolicyProcessor';
import { FileReaderProcessor } from './processors/FileReaderProcessor';
import { GithubReaderProcessor } from './processors/GithubReaderProcessor';
-import { GithubApiReaderProcessor } from './processors/GithubApiReaderProcessor';
import { GitlabApiReaderProcessor } from './processors/GitlabApiReaderProcessor';
import { GitlabReaderProcessor } from './processors/GitlabReaderProcessor';
import { BitbucketApiReaderProcessor } from './processors/BitbucketApiReaderProcessor';
@@ -67,18 +66,19 @@ export class LocationReaders implements LocationReader {
private readonly rulesEnforcer: CatalogRulesEnforcer;
static defaultProcessors(options: {
+ logger: Logger;
config?: Config;
entityPolicy?: EntityPolicy;
}): LocationProcessor[] {
const {
+ logger,
config = new ConfigReader({}, 'missing-config'),
entityPolicy = new EntityPolicies(),
} = options;
return [
StaticLocationProcessor.fromConfig(config),
new FileReaderProcessor(),
- new GithubReaderProcessor(config),
- new GithubApiReaderProcessor(config),
+ GithubReaderProcessor.fromConfig(config, logger),
new GitlabApiReaderProcessor(config),
new GitlabReaderProcessor(),
new BitbucketApiReaderProcessor(config),
@@ -94,7 +94,7 @@ export class LocationReaders implements LocationReader {
constructor({
logger = getVoidLogger(),
config,
- processors = LocationReaders.defaultProcessors({ config }),
+ processors = LocationReaders.defaultProcessors({ logger, config }),
}: Options) {
this.logger = logger;
this.processors = processors;
diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubApiReaderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/GithubApiReaderProcessor.test.ts
deleted file mode 100644
index 51c3cf4cd0..0000000000
--- a/plugins/catalog-backend/src/ingestion/processors/GithubApiReaderProcessor.test.ts
+++ /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 { GithubApiReaderProcessor } from './GithubApiReaderProcessor';
-import { ConfigReader } from '@backstage/config';
-
-describe('GithubApiReaderProcessor', () => {
- const createConfig = (token: string | undefined) =>
- ConfigReader.fromConfigs([
- {
- context: '',
- data: {
- catalog: {
- processors: {
- githubApi: {
- privateToken: token,
- },
- },
- },
- },
- },
- ]);
-
- it('should build raw api', () => {
- const processor = new GithubApiReaderProcessor(createConfig(undefined));
-
- const tests = [
- {
- target: 'https://github.com/a/b/blob/master/path/to/c.yaml',
- url: new URL(
- 'https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=master',
- ),
- err: undefined,
- },
- {
- target: 'https://api.com/a/b/blob/master/path/to/c.yaml',
- url: null,
- err:
- 'Incorrect url: https://api.com/a/b/blob/master/path/to/c.yaml, Error: Wrong GitHub URL or Invalid file path',
- },
- {
- target: 'com/a/b/blob/master/path/to/c.yaml',
- url: null,
- err:
- 'Incorrect url: com/a/b/blob/master/path/to/c.yaml, TypeError: Invalid URL: com/a/b/blob/master/path/to/c.yaml',
- },
- {
- target:
- 'https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/playback-order-component.yaml',
- url: new URL(
- 'https://api.github.com/repos/spotify/backstage/contents/packages/catalog-model/examples/playback-order-component.yaml?ref=master',
- ),
- err: undefined,
- },
- ];
-
- for (const test of tests) {
- if (test.err) {
- expect(() => processor.buildRawUrl(test.target)).toThrowError(test.err);
- } else if (test.url) {
- expect(processor.buildRawUrl(test.target).toString()).toEqual(
- test.url.toString(),
- );
- } else {
- throw new Error(
- 'This should not have happened. Either err or url should have matched.',
- );
- }
- }
- });
-
- it('should return request options', () => {
- const tests = [
- {
- token: '0123456789',
- expect: {
- headers: {
- Accept: 'application/vnd.github.v3.raw',
- Authorization: 'token 0123456789',
- },
- },
- },
- {
- token: '',
- err:
- "Invalid type in config for key 'catalog.processors.githubApi.privateToken' in '', got empty-string, wanted string",
- expect: {
- headers: {
- Accept: 'application/vnd.github.v3.raw',
- },
- },
- },
- {
- token: undefined,
- expect: {
- headers: {
- Accept: 'application/vnd.github.v3.raw',
- },
- },
- },
- ];
-
- for (const test of tests) {
- if (test.err) {
- expect(
- () => new GithubApiReaderProcessor(createConfig(test.token)),
- ).toThrowError(test.err);
- } else {
- const processor = new GithubApiReaderProcessor(
- createConfig(test.token),
- );
- expect(processor.getRequestOptions()).toEqual(test.expect);
- }
- }
- });
-});
diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubApiReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubApiReaderProcessor.ts
deleted file mode 100644
index f8d1d6caf8..0000000000
--- a/plugins/catalog-backend/src/ingestion/processors/GithubApiReaderProcessor.ts
+++ /dev/null
@@ -1,128 +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 { LocationSpec } from '@backstage/catalog-model';
-import fetch, { RequestInit, HeadersInit } from 'node-fetch';
-import * as result from './results';
-import { LocationProcessor, LocationProcessorEmit } from './types';
-import { Config } from '@backstage/config';
-
-export class GithubApiReaderProcessor implements LocationProcessor {
- private privateToken: string;
-
- constructor(config: Config) {
- this.privateToken =
- config.getOptionalString('catalog.processors.githubApi.privateToken') ??
- '';
- }
-
- getRequestOptions(): RequestInit {
- const headers: HeadersInit = {
- Accept: 'application/vnd.github.v3.raw',
- };
-
- if (this.privateToken !== '') {
- headers.Authorization = `token ${this.privateToken}`;
- }
-
- const requestOptions: RequestInit = {
- headers,
- };
-
- return requestOptions;
- }
-
- async readLocation(
- location: LocationSpec,
- optional: boolean,
- emit: LocationProcessorEmit,
- ): Promise {
- if (location.type !== 'github/api') {
- return false;
- }
-
- try {
- const url = this.buildRawUrl(location.target);
-
- const response = await fetch(url.toString(), this.getRequestOptions());
-
- if (response.ok) {
- const data = await response.buffer();
- emit(result.data(location, data));
- } else {
- const message = `${location.target} could not be read as ${url}, ${response.status} ${response.statusText}`;
- if (response.status === 404) {
- if (!optional) {
- emit(result.notFoundError(location, message));
- }
- } else {
- emit(result.generalError(location, message));
- }
- }
- } catch (e) {
- const message = `Unable to read ${location.type} ${location.target}, ${e}`;
- emit(result.generalError(location, message));
- }
-
- return true;
- }
-
- // Converts
- // from: https://github.com/a/b/blob/master/path/to/c.yaml
- // to: https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=master
- buildRawUrl(target: string): URL {
- try {
- const url = new URL(target);
-
- const [
- empty,
- userOrOrg,
- repoName,
- blobKeyword,
- ref,
- ...restOfPath
- ] = url.pathname.split('/');
-
- if (
- url.hostname !== 'github.com' ||
- empty !== '' ||
- userOrOrg === '' ||
- repoName === '' ||
- blobKeyword !== 'blob' ||
- !restOfPath.join('/').match(/\.yaml$/)
- ) {
- throw new Error('Wrong GitHub URL or Invalid file path');
- }
-
- // transform to api
- url.pathname = [
- empty,
- 'repos',
- userOrOrg,
- repoName,
- 'contents',
- ...restOfPath,
- ].join('/');
- url.hostname = 'api.github.com';
- url.protocol = 'https';
- url.search = `ref=${ref}`;
-
- return url;
- } catch (e) {
- throw new Error(`Incorrect url: ${target}, ${e}`);
- }
- }
-}
diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubReaderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/GithubReaderProcessor.test.ts
new file mode 100644
index 0000000000..3ab24541e7
--- /dev/null
+++ b/plugins/catalog-backend/src/ingestion/processors/GithubReaderProcessor.test.ts
@@ -0,0 +1,269 @@
+/*
+ * 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 { LocationSpec } from '@backstage/catalog-model';
+import { ConfigReader } from '@backstage/config';
+import {
+ getApiRequestOptions,
+ getApiUrl,
+ getRawRequestOptions,
+ getRawUrl,
+ GithubReaderProcessor,
+ ProviderConfig,
+ readConfig,
+} from './GithubReaderProcessor';
+
+describe('GithubReaderProcessor', () => {
+ describe('getApiRequestOptions', () => {
+ it('sets the correct API version', () => {
+ const config: ProviderConfig = { target: '', apiBaseUrl: '' };
+ expect((getApiRequestOptions(config).headers as any).Accept).toEqual(
+ 'application/vnd.github.v3.raw',
+ );
+ });
+
+ it('inserts a token when needed', () => {
+ const withToken: ProviderConfig = {
+ target: '',
+ apiBaseUrl: '',
+ token: 'A',
+ };
+ const withoutToken: ProviderConfig = {
+ target: '',
+ apiBaseUrl: '',
+ };
+ expect(
+ (getApiRequestOptions(withToken).headers as any).Authorization,
+ ).toEqual('token A');
+ expect(
+ (getApiRequestOptions(withoutToken).headers as any).Authorization,
+ ).toBeUndefined();
+ });
+ });
+
+ describe('getRawRequestOptions', () => {
+ it('inserts a token when needed', () => {
+ const withToken: ProviderConfig = {
+ target: '',
+ rawBaseUrl: '',
+ token: 'A',
+ };
+ const withoutToken: ProviderConfig = {
+ target: '',
+ rawBaseUrl: '',
+ };
+ expect(
+ (getRawRequestOptions(withToken).headers as any).Authorization,
+ ).toEqual('token A');
+ expect(
+ (getRawRequestOptions(withoutToken).headers as any).Authorization,
+ ).toBeUndefined();
+ });
+ });
+
+ describe('getApiUrl', () => {
+ it('rejects targets that do not look like URLs', () => {
+ const config: ProviderConfig = { target: '', apiBaseUrl: '' };
+ expect(() => getApiUrl('a/b', config)).toThrow(/Incorrect URL: a\/b/);
+ });
+
+ it('happy path for github', () => {
+ const config: ProviderConfig = {
+ target: 'https://github.com',
+ apiBaseUrl: 'https://api.github.com',
+ };
+ expect(
+ getApiUrl(
+ 'https://github.com/a/b/blob/branchname/path/to/c.yaml',
+ config,
+ ),
+ ).toEqual(
+ new URL(
+ 'https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname',
+ ),
+ );
+ expect(
+ getApiUrl(
+ 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml',
+ config,
+ ),
+ ).toEqual(
+ new URL(
+ 'https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname',
+ ),
+ );
+ });
+
+ it('happy path for ghe', () => {
+ const config: ProviderConfig = {
+ target: 'https://ghe.mycompany.net',
+ apiBaseUrl: 'https://ghe.mycompany.net/api/v3',
+ };
+ expect(
+ getApiUrl(
+ 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml',
+ config,
+ ),
+ ).toEqual(
+ new URL(
+ 'https://ghe.mycompany.net/api/v3/repos/a/b/contents/path/to/c.yaml?ref=branchname',
+ ),
+ );
+ });
+ });
+
+ describe('getRawUrl', () => {
+ it('rejects targets that do not look like URLs', () => {
+ const config: ProviderConfig = { target: '', apiBaseUrl: '' };
+ expect(() => getRawUrl('a/b', config)).toThrow(/Incorrect URL: a\/b/);
+ });
+
+ it('happy path for github', () => {
+ const config: ProviderConfig = {
+ target: 'https://github.com',
+ rawBaseUrl: 'https://raw.githubusercontent.com',
+ };
+ expect(
+ getRawUrl(
+ 'https://github.com/a/b/blob/branchname/path/to/c.yaml',
+ config,
+ ),
+ ).toEqual(
+ new URL(
+ 'https://raw.githubusercontent.com/a/b/branchname/path/to/c.yaml',
+ ),
+ );
+ });
+
+ it('happy path for ghe', () => {
+ const config: ProviderConfig = {
+ target: 'https://ghe.mycompany.net',
+ rawBaseUrl: 'https://ghe.mycompany.net/raw',
+ };
+ expect(
+ getRawUrl(
+ 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml',
+ config,
+ ),
+ ).toEqual(
+ new URL('https://ghe.mycompany.net/raw/a/b/branchname/path/to/c.yaml'),
+ );
+ });
+ });
+
+ describe('readConfig', () => {
+ function config(
+ providers: { target: string; apiBaseUrl?: string; token?: string }[],
+ ) {
+ return ConfigReader.fromConfigs([
+ {
+ context: '',
+ data: {
+ catalog: { processors: { github: { providers } } },
+ },
+ },
+ ]);
+ }
+
+ it('adds a default GitHub entry when missing', () => {
+ const output = readConfig(config([]), getVoidLogger());
+ expect(output).toEqual([
+ {
+ target: 'https://github.com',
+ apiBaseUrl: 'https://api.github.com',
+ rawBaseUrl: 'https://raw.githubusercontent.com',
+ },
+ ]);
+ });
+
+ it('injects the correct GitHub API base URL when missing', () => {
+ const output = readConfig(
+ config([{ target: 'https://github.com' }]),
+ getVoidLogger(),
+ );
+ expect(output).toEqual([
+ {
+ target: 'https://github.com',
+ apiBaseUrl: 'https://api.github.com',
+ rawBaseUrl: 'https://raw.githubusercontent.com',
+ },
+ ]);
+ });
+
+ it('rejects custom targets with no base URLs', () => {
+ expect(() =>
+ readConfig(
+ config([{ target: 'https://ghe.company.com' }]),
+ getVoidLogger(),
+ ),
+ ).toThrow(
+ 'Provider at https://ghe.company.com must configure an explicit apiBaseUrl or rawBaseUrl',
+ );
+ });
+
+ it('rejects funky configs', () => {
+ expect(() =>
+ readConfig(config([{ target: 7 } as any]), getVoidLogger()),
+ ).toThrow(/target/);
+ expect(() =>
+ readConfig(config([{ noTarget: '7' } as any]), getVoidLogger()),
+ ).toThrow(/target/);
+ expect(() =>
+ readConfig(
+ config([{ target: 'https://github.com', apiBaseUrl: 7 } as any]),
+ getVoidLogger(),
+ ),
+ ).toThrow(/apiBaseUrl/);
+ expect(() =>
+ readConfig(
+ config([{ target: 'https://github.com', token: 7 } as any]),
+ getVoidLogger(),
+ ),
+ ).toThrow(/token/);
+ });
+ });
+
+ describe('implementation', () => {
+ it('rejects unknown types', async () => {
+ const processor = new GithubReaderProcessor([
+ { target: 'https://github.com', apiBaseUrl: 'https://api.github.com' },
+ ]);
+ const location: LocationSpec = {
+ type: 'not-github/api',
+ target: 'https://github.com',
+ };
+ await expect(
+ processor.readLocation(location, false, () => {}),
+ ).resolves.toBeFalsy();
+ });
+
+ it('rejects unknown targets', async () => {
+ const processor = new GithubReaderProcessor([
+ { target: 'https://github.com', apiBaseUrl: 'https://api.github.com' },
+ ]);
+ const location: LocationSpec = {
+ type: 'github/api',
+ target: 'https://not.github.com/apa',
+ };
+ await expect(
+ processor.readLocation(location, false, () => {}),
+ ).rejects.toThrow(
+ /There is no GitHub provider that matches https:\/\/not.github.com\/apa/,
+ );
+ });
+ });
+});
diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubReaderProcessor.ts
index 9f38d782fe..4f0c66148f 100644
--- a/plugins/catalog-backend/src/ingestion/processors/GithubReaderProcessor.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/GithubReaderProcessor.ts
@@ -15,33 +15,207 @@
*/
import { LocationSpec } from '@backstage/catalog-model';
-import fetch, { RequestInit, HeadersInit } from 'node-fetch';
+import { Config } from '@backstage/config';
+import parseGitUri from 'git-url-parse';
+import fetch, { HeadersInit, RequestInit } from 'node-fetch';
+import { Logger } from 'winston';
import * as result from './results';
import { LocationProcessor, LocationProcessorEmit } from './types';
-import { Config } from '@backstage/config';
-export class GithubReaderProcessor implements LocationProcessor {
- private privateToken: string;
+/**
+ * The configuration parameters for a single GitHub API provider.
+ */
+export type ProviderConfig = {
+ /**
+ * The prefix of the target that this matches on, e.g. "https://github.com",
+ * with no trailing slash.
+ */
+ target: string;
- constructor(config?: Config) {
- this.privateToken =
- config?.getOptionalString('catalog.processors.github.privateToken') ?? '';
+ /**
+ * The base URL of the API of this provider, e.g. "https://api.github.com",
+ * with no trailing slash.
+ *
+ * May be omitted specifically for GitHub; then it will be deduced.
+ *
+ * The API will always be preferred if both its base URL and a token are
+ * present.
+ */
+ apiBaseUrl?: string;
+
+ /**
+ * The base URL of the raw fetch endpoint of this provider, e.g.
+ * "https://raw.githubusercontent.com", with no trailing slash.
+ *
+ * May be omitted specifically for GitHub; then it will be deduced.
+ *
+ * The API will always be preferred if both its base URL and a token are
+ * present.
+ */
+ rawBaseUrl?: string;
+
+ /**
+ * The authorization token to use for requests to this provider.
+ *
+ * If no token is specified, anonymous access is used.
+ */
+ token?: string;
+};
+
+export function getApiRequestOptions(provider: ProviderConfig): RequestInit {
+ const headers: HeadersInit = {
+ Accept: 'application/vnd.github.v3.raw',
+ };
+
+ if (provider.token) {
+ headers.Authorization = `token ${provider.token}`;
}
- getRequestOptions(): RequestInit {
- const headers: HeadersInit = {
- Accept: 'application/vnd.github.v3.raw',
- };
+ return {
+ headers,
+ };
+}
- if (this.privateToken !== '') {
- headers.Authorization = `token ${this.privateToken}`;
+export function getRawRequestOptions(provider: ProviderConfig): RequestInit {
+ const headers: HeadersInit = {};
+
+ if (provider.token) {
+ headers.Authorization = `token ${provider.token}`;
+ }
+
+ return {
+ headers,
+ };
+}
+
+// Converts for example
+// from: https://github.com/a/b/blob/branchname/path/to/c.yaml
+// to: https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname
+export function getApiUrl(target: string, provider: ProviderConfig): URL {
+ try {
+ const { owner, name, ref, filepathtype, filepath } = parseGitUri(target);
+
+ if (
+ !owner ||
+ !name ||
+ !ref ||
+ (filepathtype !== 'blob' && filepathtype !== 'raw') ||
+ !filepath?.match(/\.ya?ml$/)
+ ) {
+ throw new Error('Wrong URL or invalid file path');
}
- const requestOptions: RequestInit = {
- headers,
- };
+ const pathWithoutSlash = filepath.replace(/^\//, '');
+ return new URL(
+ `${provider.apiBaseUrl}/repos/${owner}/${name}/contents/${pathWithoutSlash}?ref=${ref}`,
+ );
+ } catch (e) {
+ throw new Error(`Incorrect URL: ${target}, ${e}`);
+ }
+}
- return requestOptions;
+// Converts for example
+// from: https://github.com/a/b/blob/branchname/c.yaml
+// to: https://raw.githubusercontent.com/a/b/branchname/c.yaml
+export function getRawUrl(target: string, provider: ProviderConfig): URL {
+ try {
+ const { owner, name, ref, filepathtype, filepath } = parseGitUri(target);
+
+ if (
+ !owner ||
+ !name ||
+ !ref ||
+ (filepathtype !== 'blob' && filepathtype !== 'raw') ||
+ !filepath?.match(/\.ya?ml$/)
+ ) {
+ throw new Error('Wrong URL or invalid file path');
+ }
+
+ const pathWithoutSlash = filepath.replace(/^\//, '');
+ return new URL(
+ `${provider.rawBaseUrl}/${owner}/${name}/${ref}/${pathWithoutSlash}`,
+ );
+ } catch (e) {
+ throw new Error(`Incorrect URL: ${target}, ${e}`);
+ }
+}
+
+export function readConfig(config: Config, logger: Logger): ProviderConfig[] {
+ const providers: ProviderConfig[] = [];
+
+ // TODO(freben): Deprecate the old config root entirely in a later release
+ if (config.has('catalog.processors.githubApi')) {
+ logger.warn(
+ 'The catalog.processors.githubApi configuration key has been deprecated, please use catalog.processors.github instead',
+ );
+ }
+
+ // In a previous version of the configuration, we only supported github,
+ // and the "privateToken" key held the token to use for it. The new
+ // configuration method is to use the "providers" key instead.
+ const providerConfigs =
+ config.getOptionalConfigArray('catalog.processors.github.providers') ??
+ config.getOptionalConfigArray('catalog.processors.githubApi.providers') ??
+ [];
+ const legacyToken =
+ config.getOptionalString('catalog.processors.github.privateToken') ??
+ config.getOptionalString('catalog.processors.githubApi.privateToken');
+
+ // First read all the explicit providers
+ for (const providerConfig of providerConfigs) {
+ const target = providerConfig.getString('target').replace(/\/+$/, '');
+ let apiBaseUrl = providerConfig.getOptionalString('apiBaseUrl');
+ let rawBaseUrl = providerConfig.getOptionalString('rawBaseUrl');
+ const token = providerConfig.getOptionalString('token');
+
+ if (apiBaseUrl) {
+ apiBaseUrl = apiBaseUrl.replace(/\/+$/, '');
+ } else if (target === 'https://github.com') {
+ apiBaseUrl = 'https://api.github.com';
+ }
+
+ if (rawBaseUrl) {
+ rawBaseUrl = rawBaseUrl.replace(/\/+$/, '');
+ } else if (target === 'https://github.com') {
+ rawBaseUrl = 'https://raw.githubusercontent.com';
+ }
+
+ if (!apiBaseUrl && !rawBaseUrl) {
+ throw new Error(
+ `Provider at ${target} must configure an explicit apiBaseUrl or rawBaseUrl`,
+ );
+ }
+
+ providers.push({ target, apiBaseUrl, rawBaseUrl, token });
+ }
+
+ // If no explicit github.com provider was added, put one in the list as
+ // a convenience
+ if (!providers.some(p => p.target === 'https://github.com')) {
+ providers.push({
+ target: 'https://github.com',
+ apiBaseUrl: 'https://api.github.com',
+ rawBaseUrl: 'https://raw.githubusercontent.com',
+ token: legacyToken,
+ });
+ }
+
+ return providers;
+}
+
+/**
+ * A processor that adds the ability to read files from GitHub v3 APIs, such as
+ * the one exposed by GitHub itself.
+ */
+export class GithubReaderProcessor implements LocationProcessor {
+ private providers: ProviderConfig[];
+
+ static fromConfig(config: Config, logger: Logger) {
+ return new GithubReaderProcessor(readConfig(config, logger));
+ }
+
+ constructor(providers: ProviderConfig[]) {
+ this.providers = providers;
}
async readLocation(
@@ -49,16 +223,30 @@ export class GithubReaderProcessor implements LocationProcessor {
optional: boolean,
emit: LocationProcessorEmit,
): Promise {
- if (location.type !== 'github') {
+ // The github/api type is for backward compatibility
+ if (location.type !== 'github' && location.type !== 'github/api') {
return false;
}
- try {
- const url = this.buildRawUrl(location.target);
+ const provider = this.providers.find(p =>
+ location.target.startsWith(`${p.target}/`),
+ );
+ if (!provider) {
+ throw new Error(
+ `There is no GitHub provider that matches ${location.target}. Please add a configuration entry for it under catalog.processors.github.providers.`,
+ );
+ }
- // TODO(freben): Should "hard" errors thrown by this line be treated as
- // notFound instead of fatal?
- const response = await fetch(url.toString(), this.getRequestOptions());
+ try {
+ const useApi =
+ provider.apiBaseUrl && (provider.token || !provider.rawBaseUrl);
+ const url = useApi
+ ? getApiUrl(location.target, provider)
+ : getRawUrl(location.target, provider);
+ const options = useApi
+ ? getApiRequestOptions(provider)
+ : getRawRequestOptions(provider);
+ const response = await fetch(url.toString(), options);
if (response.ok) {
const data = await response.buffer();
@@ -80,41 +268,4 @@ export class GithubReaderProcessor implements LocationProcessor {
return true;
}
-
- // Converts
- // from: https://github.com/a/b/blob/master/c.yaml
- // to: https://raw.githubusercontent.com/a/b/master/c.yaml
- private buildRawUrl(target: string): URL {
- try {
- const url = new URL(target);
-
- const [
- empty,
- userOrOrg,
- repoName,
- blobKeyword,
- ...restOfPath
- ] = url.pathname.split('/');
-
- if (
- url.hostname !== 'github.com' ||
- empty !== '' ||
- userOrOrg === '' ||
- repoName === '' ||
- blobKeyword !== 'blob' ||
- !restOfPath.join('/').match(/\.yaml$/)
- ) {
- throw new Error('Wrong GitHub URL');
- }
-
- // Removing the "blob" part
- url.pathname = [empty, userOrOrg, repoName, ...restOfPath].join('/');
- url.hostname = 'raw.githubusercontent.com';
- url.protocol = 'https';
-
- return url;
- } catch (e) {
- throw new Error(`Incorrect url: ${target}, ${e}`);
- }
- }
}
diff --git a/plugins/catalog-graphql/.eslintrc.js b/plugins/catalog-graphql/.eslintrc.js
new file mode 100644
index 0000000000..16a033dbc6
--- /dev/null
+++ b/plugins/catalog-graphql/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: [require.resolve('@backstage/cli/config/eslint.backend')],
+};
diff --git a/plugins/catalog-graphql/README.md b/plugins/catalog-graphql/README.md
new file mode 100644
index 0000000000..911d4a401c
--- /dev/null
+++ b/plugins/catalog-graphql/README.md
@@ -0,0 +1,11 @@
+# Catalog GraphQL Plugin
+
+## Getting Started
+
+This is the Catalog GraphQL plugin.
+
+It provides the `catalog` part of the GraphQL schema.
+
+To register it with the GraphQL backend, be sure to follow the [Getting Started](../graphql/README.md#getting-started) guide of the GraphQL plugin.
+
+