diff --git a/README.md b/README.md index 9a1622cda7..ee9e85fd7f 100644 --- a/README.md +++ b/README.md @@ -18,26 +18,16 @@ Backstage unifies all your infrastructure tooling, services, and documentation t Out of the box, Backstage includes: -- [Backstage Service Catalog](https://github.com/spotify/backstage/blob/master/docs/features/software-catalog/index.md) for managing all your software (microservices, libraries, data pipelines, websites, ML models, etc.) -- [Backstage Software Templates](https://github.com/spotify/backstage/blob/master/docs/features/software-templates/index.md) for quickly spinning up new projects and standardizing your tooling with your organization’s best practices -- [Backstage TechDocs](https://github.com/spotify/backstage/tree/master/docs/features/techdocs) for making it easy to create, maintain, find, and use technical documentation, using a "docs like code" approach +- [Backstage Service Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) for managing all your software (microservices, libraries, data pipelines, websites, ML models, etc.) +- [Backstage Software Templates](https://backstage.io/docs/features/software-templates/software-templates-index) for quickly spinning up new projects and standardizing your tooling with your organization’s best practices +- [Backstage TechDocs](https://backstage.io/docs/features/techdocs/techdocs-overview) for making it easy to create, maintain, find, and use technical documentation, using a "docs like code" approach - Plus, a growing ecosystem of [open source plugins](https://github.com/spotify/backstage/tree/master/plugins) that further expand Backstage’s customizability and functionality For more information go to [backstage.io](https://backstage.io) or join our [Discord chatroom](https://discord.gg/EBHEGzX). ## Project roadmap -We created Backstage about 4 years ago. While our internal version of Backstage has had the benefit of time to mature and evolve, the first iteration of our open source version is still nascent. We are envisioning three phases of the project and we have already begun work on various aspects of these phases: - -- 🐣 **Phase 1:** Extensible frontend platform (Done ✅) - You will be able to easily create a single consistent UI layer for your internal infrastructure and tools. A set of reusable [UX patterns and components](http://backstage.io/storybook) help ensure a consistent experience between tools. - -- 🐢 **Phase 2:** Service Catalog ([alpha released](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha)) - With a single catalog, Backstage makes it easy for a team to manage ten services — and makes it possible for your company to manage thousands of them. Developers can get a uniform overview of all their software and related resources, regardless of how and where they are running, as well as an easy way to onboard and manage those resources. - -- 🐇 **Phase 3:** Ecosystem (later) - Everyone's infrastructure stack is different. By fostering a vibrant community of contributors we hope to provide an ecosystem of Open Source plugins/integrations that allows you to pick the tools that match your stack. - -Check out our [Milestones](https://github.com/spotify/backstage/milestones) and open [RFCs](https://github.com/spotify/backstage/labels/rfc) how they relate to the three Phases outlined above. - -Our vision for Backstage is for it to become the trusted standard toolbox (read: UX layer) for the open source infrastructure landscape. Think of it like Kubernetes for developer experience. We realize this is an ambitious goal. We can’t do it alone. If this sounds interesting or you'd like to help us shape our product vision, we'd love to talk. You can email me directly: [alund@spotify.com](mailto:alund@spotify.com). +A detailed project roadmap, including already delivered milestones, is available [here](https://backstage.io/docs/overview/roadmap). ## Overview @@ -69,7 +59,7 @@ Using `npx` you can then run the following to create an app in a chosen subdirec npx @backstage/create-app ``` -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](docs/getting-started/create-an-app.md) +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) ### Contributing to Backstage @@ -77,22 +67,22 @@ You can read more in our [CONTRIBUTING.md](./CONTRIBUTING.md#get-started) guide, ### Next steps -Take a look at the [Getting Started](docs/getting-started/index.md) guide to learn how to set up Backstage, and how to develop on the platform. +Take a look at the [Getting Started](https://backstage.io/docs/getting-started/index) guide to learn how to set up Backstage, and how to develop on the platform. ## Documentation -- [Main documentation](docs/README.md) -- [Service Catalog](docs/features/software-catalog/index.md) -- [Architecture](docs/overview/architecture-terminology.md) ([Decisions](docs/architecture-decisions/index.md)) -- [Designing for Backstage](docs/dls/design.md) -- [Storybook - UI components](http://backstage.io/storybook) +- [Main documentation](https://backstage.io/docs/overview/what-is-backstage) +- [Service Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) +- [Architecture](https://backstage.io/docs/overview/architecture-terminology) ([Decisions](https://backstage.io/docs/architecture-decisions/adrs-overview)) +- [Designing for Backstage](https://backstage.io/docs/dls/design) +- [Storybook - UI components](https://backstage.io/storybook) ## Community - [Discord chatroom](https://discord.gg/MUpMjP2) - Get support or discuss the project - [Good First Issues](https://github.com/spotify/backstage/contribute) - Start here if you want to contribute - [RFCs](https://github.com/spotify/backstage/labels/rfc) - Help shape the technical direction -- [FAQ](docs/FAQ.md) - Frequently Asked Questions +- [FAQ](https://backstage.io/docs/FAQ) - Frequently Asked Questions - [Code of Conduct](CODE_OF_CONDUCT.md) - This is how we roll - [Adopters](ADOPTERS.md) - Companies already using Backstage - [Blog](https://backstage.io/blog/) - Announcements and updates diff --git a/docs/README.md b/docs/README.md index 9bbc69ca6d..b9e79a9e0a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,6 +11,7 @@ better yet, a pull request. - [Backstage architecture](overview/architecture-overview.md) - [Architecture and terminology](overview/architecture-terminology.md) - [Roadmap](overview/roadmap.md) + - [Vision](overview/vision.md) - Getting started - [Running Backstage locally](getting-started/index.md) - [Installation](getting-started/installation.md) diff --git a/docs/auth/auth-backend-classes.md b/docs/auth/auth-backend-classes.md index 1a476fa2f0..42ee48c5e0 100644 --- a/docs/auth/auth-backend-classes.md +++ b/docs/auth/auth-backend-classes.md @@ -1,11 +1,11 @@ -# Authentication backend class layout and description. +# Authentication Backend Classes Layout and Description -## How does authentication work ? +## How Does Authentication Work? -The Backstage application can use various authentication `providers` for -authentication. A provider has to implement an `AuthProviderRouterHandlers` -interface for handling authentication. This interface consists of 4 methods. -Each of this method is hosted at an endpoint `/auth/[provider]/method`, where +The Backstage application can use various authentication providers for +authentication. A provider has to implement an `AuthProviderRouteHandlers` +interface for handling authentication. This interface consists of four methods. +Each of these methods is hosted at an endpoint `/auth/[provider]/method`, where `method` performs a certain operation as follows: ``` @@ -16,44 +16,45 @@ Each of this method is hosted at an endpoint `/auth/[provider]/method`, where ``` For more information on how these methods are used and for which purpose, refer -to the documentation [here](oauth.md) +to the documentation [here](oauth.md). For details on the parameters, input and output conditions for each method, refer to the type documentation under -`backstage/plugins/auth-backend/src/providers/types.ts` +`plugins/auth-backend/src/providers/types.ts`. -There are currently 2 different classes for 2 authentication mechanisms that -implement this interface: `OAuthProvider` for `OAuth` based Mechanism and a -`SAMLAuthProvider` for a `SAML` based mechanism +There are currently two different classes for two authentication mechanisms that +implement this interface: an `OAuthProvider` for [OAuth](https://oauth.net/2/) +based mechanisms and a `SAMLAuthProvider` for +[SAML](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) +based mechanisms. -### `OAuth` mechanisms +### OAuth mechanisms -Currently `OAuth` is assumed to be the defacto authentication mechanism for -backstage based applications. +Currently OAuth is assumed to be the de facto authentication mechanism for +Backstage based applications. -Backstage comes with `batteries-included` set of OAuth Providers for some -commonly used Providers : `Okta`, `Github`, `Google` , `Gitlab` and a generic -`oauth2` provider. +Backstage comes with a "batteries-included" set of supported commonly used OAuth +providers: Okta, Github, Google, Gitlab, and a generic OAuth2 provider. -All of these use the `authorization` flow of OAuth2 to implement authentication. +All of these use the authorization flow of OAuth2 to implement authentication. -If your `authentication` provider is any of the above mentioned (except -`oauth2`) providers, you can configure them by setting the right variables in -`app-config.yaml` under then `auth` section. +If your authentication provider is any of the above mentioned (except generic +OAuth2) providers, you can configure them by setting the right variables in +`app-config.yaml` under the `auth` section. ### Configuration -Each authentication (except SAML )provider needs 5 parameters: an `oauth` -client_id, client_secret, an authorization endpoint and a token endpoint, and an -app origin. The `appOrigin` value is the URL at which the frontend of the -application is hosted. This is required because, the application opens a popup -window to perform the authentication and once the flow is completed, the popup +Each authentication provider (except SAML) needs five parameters: an OAuth +client ID, a client secret, an authorization endpoint and a token endpoint, and +an app origin. The app origin is the URL at which the frontend of the +application is hosted. This is required because the application opens a popup +window to perform the authentication, and once the flow is completed, the popup window sends a `postMessage` to the frontend application to indicate the result of the operation. Also this URL is used to verify that authentication requests are coming from only this endpoint. These values are configured via the `app-config.yaml` present in the root of -your app folder +your app folder. ``` auth: @@ -87,36 +88,37 @@ auth: secure: false clientId: $secret: + ... ``` -## Technical notes +## Technical Notes ### EnvironmentHandler -The concept of an `env` is core to the way the `auth-backend` works. `Spotify` -uses an `env` query parameter to identify the environment in which the -application is running (`dev`, `staging`, `prod`, etc). Each runtime can support +The concept of an "env" is core to the way the auth backend works. It uses an +`env` query parameter to identify the environment in which the application is +running (`development`, `staging`, `production`, etc). Each runtime can support multiple environments at the same time and the right handler for each request is identified and dispatched to based on the `env` parameter. All -`AuthProviderRouterHandlers` are wrapped within a `EnvironmentHandler`. +`AuthProviderRouteHandlers` are wrapped within an `EnvironmentHandler`. -An `EnvironmentHandler` takes an `id` for each provider that it wraps, the -handlers for each of the `env` the provider is supported in, and a `function` -that given a `Request` as argument, can extract the information about the `env` -under which it should be processed. +An `EnvironmentHandler` takes an ID for each provider that it wraps, the +handlers for each env the provider is supported in, and a function that, given a +`Request` as argument, can extract the information about the env under which it +should be processed. -Each provider exposes a factory function `createXProvider` (where X = name of -the Provider) that takes the globalconfig, env and other parameters and returns -a `AuthProviderRouteHandler` for each env, AND, a `envIdentifier` fn to identify -the `env` in a request. +Each provider exposes a factory function `createXProvider` (where X is the name +of the provider) that takes the global config, env and other parameters and +returns an `AuthProviderRouteHandlers` for each env, and an `envIdentifier` +function to identify the env of a request. For a list of currently available providers, look in the `factories` module -located in `backstage/plugins/auth-backend/src/providers/factories.ts` +located in `plugins/auth-backend/src/providers/factories.ts` -### `oauth2` provider +### OAuth2 provider -The `oauth2` provider abstracts a generic **OAuth2+OIDC** based authentication +The `oauth2` provider abstracts a generic **OAuth2 + OIDC** based authentication provider. What this means is that after the application has been given -permission by the user, the`authorization code` will be exchanged for an -`access_token` , a `refresh_token` and an `id_token`. This `id_token` is used to +permission by the user, the `authorization code` will be exchanged for an +`access_token`, a `refresh_token` and an `id_token`. This `id_token` is used to obtain an email id of the user, which is then used for creating the session. diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 4751bd88f7..a7d57890dc 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -42,6 +42,7 @@ software catalog API. "labels": { "system": "public-websites" }, + "tags": ["java"], "name": "artist-web", "uid": "2152f463-549d-4d8d-a94d-ce2b7676c6e2" }, @@ -66,6 +67,8 @@ metadata: annotations: example.com/service-discovery: artistweb circleci.com/project-slug: gh/example-org/artist-website + tags: + - java spec: type: website lifecycle: production @@ -232,6 +235,20 @@ The `backstage.io/` prefix is reserved for use by Backstage core components. Values can be of any length, but are limited to being strings. +### `tags` [optional] + +A list of single-valued strings, for example to classify catalog entities in +various ways. This is different to the labels in metadata, as labels are +key-value pairs. + +The values are user defined, for example the programming language used for the +component, like `java` or `go`. + +This field is optional, and currently has no special semantics. + +Each tag must be sequences of `[a-zA-Z0-9]` separated by `-`, at most 63 +characters in total. + ## Kind: Component Describes the following entity kind: diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index 6acea71509..cfe43e3e86 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -3,37 +3,125 @@ id: roadmap title: Project roadmap --- -We created Backstage about 4 years ago. While our internal version of Backstage -has had the benefit of time to mature and evolve, the first iteration of our -open source version is still nascent. We are envisioning three phases of the -project and we have already begun work on various aspects of these phases: +## Current status + +> Backstage is currently in Alpha. This means that you can expect APIs and +> features to change until we reach our +> [Backstage Beta milestone](https://github.com/spotify/backstage/milestone/19). +> Before we have API stability, plugins will need ongoing maintenance. It is +> also recommended that teams who adopt Backstage today upgrades their +> installation as new [releases](https://github.com/spotify/backstage/releases) +> are available. + +## Phases + +We have divided the project into three high-level _phases_: - 🐣 **Phase 1:** Extensible frontend platform (Done ✅) - You will be able to easily create a single consistent UI layer for your internal infrastructure and tools. A set of reusable - [UX patterns and components](http://backstage.io/storybook) help ensure a + [UX patterns and components](https://backstage.io/storybook) help ensure a consistent experience between tools. - 🐢 **Phase 2:** Service Catalog ([alpha released](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha)) - With a single catalog, Backstage makes it easy for a team to manage ten services — and makes it possible for your company to manage thousands of them. - Developers can get a uniform overview of all their software and related - resources, regardless of how and where they are running, as well as an easy - way to onboard and manage those resources. - 🐇 **Phase 3:** Ecosystem (later) - Everyone's infrastructure stack is different. By fostering a vibrant community of contributors we hope to provide an ecosystem of Open Source plugins/integrations that allows you to pick the tools that match your stack. -Check out our [Milestones](https://github.com/spotify/backstage/milestones) and -open [RFCs](https://github.com/spotify/backstage/labels/rfc) how they relate to -the three Phases outlined above. +## Detailed roadmap -Our vision for Backstage is for it to become the trusted standard toolbox (read: -UX layer) for the open source infrastructure landscape. Think of it like -Kubernetes for developer experience. We realize this is an ambitious goal. We -can’t do it alone. If this sounds interesting or you'd like to help us shape our -product vision, we'd love to talk. You can email me directly: +If you have questions about the roadmap or want to provide feedback, we would +love to hear from you! Please create an +[Issue](https://github.com/spotify/backstage/issues/new/choose), ping us on +[Discord](https://discord.gg/EBHEGzX) or reach out directly at [alund@spotify.com](mailto:alund@spotify.com). + +Want to help out? Awesome ❤️ Head over to +[CONTRIBUTING](https://github.com/spotify/backstage/blob/master/CONTRIBUTING.md) +guidelines to get started. + +### Ongoing work 🚧 + +- **[Plugins for managing micro services end-2-end](https://github.com/spotify/backstage/milestone/14)** - + Out of the box Backstage will ship with a set of plugins (Overview, CI, API + and Docs) that will demonstrate how a user can manage a micro service and + follow a change all the way out in production. Completing this work will make + it much easier to see how a plugin can be built that integrates with the + Backstage Service Catalog. + +- **Backstage Design System** - By providing design guidelines for common plugin + layouts together, rich set of reusable UI components + ([Storybook](https://backstage.io/storybook)) and Figma design resources. The + Design System will make it easy to design and build plugins that are + consistent across the platform -- supporting both developers and designers. + +- **[TechDocs v1](https://github.com/spotify/backstage/milestone/16)** - Our + docs-like-code feature TechDocs working end to end. + +- **[Initial GraphQL API](https://github.com/spotify/backstage/milestone/13)** - + A GraphQL API will open up the rich metadata provided by Backstage in a single + query. Plugins can easily query this API as well as extend the model where + needed. + +- **Production deployments** - Provide instructions and default configurations + (e.g. through Helm charts) for easy deployments of Backstage and its + subsystems on Kubernetes. + +- **Cloud Cost Insights plugin (from Spotify)** - Spotify teams are fully + responsible for their own software, including the cost of the cloud resources + they use. By making our internal cost insights plugin available as open source + you will also be able to treat cost as an engineering problem, and make it + easy for your engineers to see their spend and where there's opportunity to + reduce waste. + +- Further improvements to platform documentation + +### Future work 🔮 + +- **[Backstage Beta](https://github.com/spotify/backstage/milestone/19)** - The + platform APIs and features are stable and can be dependent on for production + use. After this plugins will require little to no maintenance. + +- **[Plugin marketplace](https://github.com/spotify/backstage/issues/2009)** - + As the ecosystem of Backstage plugins continues to grow it is becoming + increasingly hard to keep track of what plugins are available. To solve this + we imagine a "Plugin marketplace" that helps with discovery and installation + of plugins. + +- **Deploy a product demo at `demo.backstage.io`** - Deploy a typical Backstage + deployment available publicly so that people can click around and get a feel + for the product without having to install anything. + +- **[Global search](https://github.com/spotify/backstage/issues/1499)** - Extend + the basic search available in the Backstage Service Catalog with a global + search experience. Long term this search solution should be extensible, making + it possible for you add custom search results. + +- **[[TechDocs V.2] Stabilization release](https://github.com/spotify/backstage/milestone/17)** - + Platform stability and compatibility improvements. + +- **Additional auth providers** - Backstage should work for most (all!) auth + solutions. Since Backstage can be used by companies regardless of what cloud + (or on prem) you are using we are especially keen to get auth support for + [AWS](https://github.com/spotify/backstage/issues/290), + [Azure](https://github.com/spotify/backstage/issues/348) and others. + +### Completed milestones ✅ + +- [Improved and move documentation to backstage.io](https://backstage.io/docs/overview/what-is-backstage) +- [Backstage Service Catalog (alpha)](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha) +- [Backstage Software Templates (alpha)](https://backstage.io/blog/2020/08/05/announcing-backstage-software-templates) +- [Make it possible to add custom auth providers](https://backstage.io/blog/2020/07/01/how-to-enable-authentication-in-backstage-using-passport) +- [TechDocs v0](https://github.com/spotify/backstage/milestone/15) +- CI plugins: CircleCI, Jenkins, GitHub Actions and TravisCI +- [Service API documentation](https://github.com/spotify/backstage/pull/1737) +- Backstage Service Catalog can read from: GitHub, GitLab, + [Bitbucket](https://github.com/spotify/backstage/pull/1938) +- Support auth providers: Google, Okta, GitHub, GitLab, + [auth0](https://github.com/spotify/backstage/pull/1611), + [AWS](https://github.com/spotify/backstage/pull/1990) diff --git a/docs/overview/vision.md b/docs/overview/vision.md new file mode 100644 index 0000000000..2ab7cdc6f4 --- /dev/null +++ b/docs/overview/vision.md @@ -0,0 +1,21 @@ +--- +id: vision +title: Vision +--- + +Our goal is to provide engineers with the best developer experience in the +world. + +A fantastic developer experience leads to happy, creative and productive +engineers. Our belief is that engineers should not have to be experts in various +infrastructure tools to be productive. Infrastructure should be abstracted away +so that you can return to building and scaling, quickly and safely. + +![](https://backstage.io/animations/backstage-logos-hero-8.gif) + +We are working on making Backstage the trusted standard toolbox (read: UX layer) +for the open source infrastructure landscape. Think of it like Kubernetes for +developer experience. We realize this is an ambitious goal. We can’t do it +alone. If this sounds interesting or you'd like to help us shape our product +vision, we'd love to talk. You can email me directly: +[alund@spotify.com](mailto:alund@spotify.com). diff --git a/docs/overview/what-is-backstage.md b/docs/overview/what-is-backstage.md index 163b6c219b..665e172c0b 100644 --- a/docs/overview/what-is-backstage.md +++ b/docs/overview/what-is-backstage.md @@ -28,9 +28,6 @@ Out of the box, Backstage includes: [open source plugins](https://github.com/spotify/backstage/tree/master/plugins) that further expand Backstage’s customizability and functionality -For more information go to [backstage.io](https://backstage.io) or join our -[Discord chatroom](https://discord.gg/EBHEGzX). - ### Benefits - For _engineering managers_, it allows you to maintain standards and best @@ -45,3 +42,6 @@ For more information go to [backstage.io](https://backstage.io) or join our - For _everyone_, it’s a single, consistent experience that ties all your infrastructure tooling, resources, standards, owners, contributors, and administrators together in one place. + +If you have questions or want support, please join our +[Discord chatroom](https://discord.gg/EBHEGzX). diff --git a/microsite/README copy.md b/microsite/README copy.md deleted file mode 100644 index d7187888a3..0000000000 --- a/microsite/README copy.md +++ /dev/null @@ -1,182 +0,0 @@ -This website was created with [Docusaurus](https://docusaurus.io/). - -# What's In This Document - -- [Get Started in 5 Minutes](#get-started-in-5-minutes) -- [Directory Structure](#directory-structure) -- [Editing Content](#editing-content) -- [Adding Content](#adding-content) -- [Full Documentation](#full-documentation) - -## Directory Structure - -Your project file structure should look something like this - -``` -my-docusaurus/ - docs/ - doc-1.md - doc-2.md - doc-3.md - website/ - blog/ - 2016-3-11-oldest-post.md - 2017-10-24-newest-post.md - core/ - node_modules/ - pages/ - static/ - css/ - img/ - package.json - sidebars.json - siteConfig.js -``` - -# Editing Content - -## Editing an existing docs page - -Edit docs by navigating to `docs/` and editing the corresponding document: - -`docs/doc-to-be-edited.md` - -```markdown ---- -id: page-needs-edit -title: This Doc Needs To Be Edited ---- - -Edit me... -``` - -For more information about docs, click [here](https://docusaurus.io/docs/en/navigation) - -## Editing an existing blog post - -Edit blog posts by navigating to `website/blog` and editing the corresponding post: - -`website/blog/post-to-be-edited.md` - -```markdown ---- -id: post-needs-edit -title: This Blog Post Needs To Be Edited ---- - -Edit me... -``` - -For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) - -# Adding Content - -## Adding a new docs page to an existing sidebar - -1. Create the doc as a new markdown file in `/docs`, example `docs/newly-created-doc.md`: - -```md ---- -id: newly-created-doc -title: This Doc Needs To Be Edited ---- - -My new content here.. -``` - -1. Refer to that doc's ID in an existing sidebar in `website/sidebars.json`: - -```javascript -// Add newly-created-doc to the Getting Started category of docs -{ - "docs": { - "Getting Started": [ - "quick-start", - "newly-created-doc" // new doc here - ], - ... - }, - ... -} -``` - -For more information about adding new docs, click [here](https://docusaurus.io/docs/en/navigation) - -## Adding a new blog post - -1. Make sure there is a header link to your blog in `website/siteConfig.js`: - -`website/siteConfig.js` - -```javascript -headerLinks: [ - ... - { blog: true, label: 'Blog' }, - ... -] -``` - -2. Create the blog post with the format `YYYY-MM-DD-My-Blog-Post-Title.md` in `website/blog`: - -`website/blog/2018-05-21-New-Blog-Post.md` - -```markdown ---- -author: Frank Li -authorURL: https://twitter.com/foobarbaz -authorFBID: 503283835 -title: New Blog Post ---- - -Lorem Ipsum... -``` - -For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) - -## Adding items to your site's top navigation bar - -1. Add links to docs, custom pages or external links by editing the headerLinks field of `website/siteConfig.js`: - -`website/siteConfig.js` - -```javascript -{ - headerLinks: [ - ... - /* you can add docs */ - { doc: 'my-examples', label: 'Examples' }, - /* you can add custom pages */ - { page: 'help', label: 'Help' }, - /* you can add external links */ - { href: 'https://github.com/facebook/docusaurus', label: 'GitHub' }, - ... - ], - ... -} -``` - -For more information about the navigation bar, click [here](https://docusaurus.io/docs/en/navigation) - -## Adding custom pages - -1. Docusaurus uses React components to build pages. The components are saved as .js files in `website/pages/en`: -1. If you want your page to show up in your navigation header, you will need to update `website/siteConfig.js` to add to the `headerLinks` element: - -`website/siteConfig.js` - -```javascript -{ - headerLinks: [ - ... - { page: 'my-new-custom-page', label: 'My New Custom Page' }, - ... - ], - ... -} -``` - -For more information about custom pages, click [here](https://docusaurus.io/docs/en/custom-pages). - -# Full Documentation - -Full documentation can be found on the [website](https://docusaurus.io/). diff --git a/microsite/core/Footer.js b/microsite/core/Footer.js index c44b8196a6..407bebca84 100644 --- a/microsite/core/Footer.js +++ b/microsite/core/Footer.js @@ -32,22 +32,15 @@ class Footer extends React.Component {
Docs
+ What is Backstage? + Getting started - Create a Plugin - - - Structure of a Plugin - - - API references - - - FAQ + Service Catalog + Create a Plugin + Designing for Backstage
Community
@@ -55,18 +48,6 @@ class Footer extends React.Component { Subscribe to our newsletter - {/* -
Docs
- - Developing a Plugin - - - API Overview - - - Layout Components - - */}
More
diff --git a/microsite/i18n/en.json b/microsite/i18n/en.json index af891426db..40ad25fe53 100644 --- a/microsite/i18n/en.json +++ b/microsite/i18n/en.json @@ -186,6 +186,9 @@ "overview/support": { "title": "Support and community" }, + "overview/vision": { + "title": "Vision" + }, "overview/what-is-backstage": { "title": "What is Backstage?" }, diff --git a/microsite/package.json b/microsite/package.json index 6a10628b5a..dd8957245a 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -6,7 +6,7 @@ "scripts": { "examples": "docusaurus-examples", "start": "docusaurus-start", - "build": "docusaurus-build && cp static/img/*.svg build/backstage/img/", + "build": "docusaurus-build", "publish-gh-pages": "docusaurus-publish", "write-translations": "docusaurus-write-translations", "version": "docusaurus-version", diff --git a/microsite/sidebars.json b/microsite/sidebars.json index d407340471..b07f326086 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -4,7 +4,8 @@ "overview/what-is-backstage", "overview/architecture-overview", "overview/architecture-terminology", - "overview/roadmap" + "overview/roadmap", + "overview/vision" ], "Getting Started": [ "getting-started/index", diff --git a/mkdocs.yml b/mkdocs.yml index 27335903de..085d83b7cc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,6 +7,7 @@ nav: - Backstage architecture: 'overview/architecture-overview.md' - Architecture and terminology: 'overview/architecture-terminology.md' - Roadmap: 'overview/roadmap.md' + - Vision: 'overview/vision.md' - Getting started: - Running Backstage locally: 'getting-started/index.md' - Installation: 'getting-started/installation.md' diff --git a/packages/catalog-model/examples/artist-lookup-component.yaml b/packages/catalog-model/examples/artist-lookup-component.yaml index 448dd13fa3..79dd3bccf6 100644 --- a/packages/catalog-model/examples/artist-lookup-component.yaml +++ b/packages/catalog-model/examples/artist-lookup-component.yaml @@ -3,6 +3,9 @@ kind: Component metadata: name: artist-lookup description: Artist Lookup + tags: + - java + - data spec: type: service lifecycle: experimental diff --git a/packages/catalog-model/examples/petstore-component.yaml b/packages/catalog-model/examples/petstore-component.yaml index 5cf0d1f270..7e2093ad09 100644 --- a/packages/catalog-model/examples/petstore-component.yaml +++ b/packages/catalog-model/examples/petstore-component.yaml @@ -7,7 +7,7 @@ spec: type: service lifecycle: experimental owner: pets@example.com - implementedApis: + implementsApis: - petstore - streetlights - hello-world diff --git a/packages/catalog-model/examples/podcast-api-component.yaml b/packages/catalog-model/examples/podcast-api-component.yaml index 19f9a3d4de..c1b1c9281c 100644 --- a/packages/catalog-model/examples/podcast-api-component.yaml +++ b/packages/catalog-model/examples/podcast-api-component.yaml @@ -3,6 +3,8 @@ kind: Component metadata: name: podcast-api description: Podcast API + tags: + - java spec: type: service lifecycle: experimental diff --git a/packages/catalog-model/examples/queue-proxy-component.yaml b/packages/catalog-model/examples/queue-proxy-component.yaml index a9f41b8776..c9b130db52 100644 --- a/packages/catalog-model/examples/queue-proxy-component.yaml +++ b/packages/catalog-model/examples/queue-proxy-component.yaml @@ -3,6 +3,9 @@ kind: Component metadata: name: queue-proxy description: Queue Proxy + tags: + - go + - website spec: type: website lifecycle: production diff --git a/packages/catalog-model/examples/searcher-component.yaml b/packages/catalog-model/examples/searcher-component.yaml index 33d765117c..042fcb24a9 100644 --- a/packages/catalog-model/examples/searcher-component.yaml +++ b/packages/catalog-model/examples/searcher-component.yaml @@ -3,6 +3,8 @@ kind: Component metadata: name: searcher description: Searcher + tags: + - go spec: type: service lifecycle: production diff --git a/packages/catalog-model/examples/shuffle-api-component.yaml b/packages/catalog-model/examples/shuffle-api-component.yaml index 275c48d6ba..1f9de46d4e 100644 --- a/packages/catalog-model/examples/shuffle-api-component.yaml +++ b/packages/catalog-model/examples/shuffle-api-component.yaml @@ -3,6 +3,8 @@ kind: Component metadata: name: shuffle-api description: Shuffle API + tags: + - go spec: type: service lifecycle: production diff --git a/packages/catalog-model/examples/streetlights-api.yaml b/packages/catalog-model/examples/streetlights-api.yaml index 69a6d101cb..4aeef993bb 100644 --- a/packages/catalog-model/examples/streetlights-api.yaml +++ b/packages/catalog-model/examples/streetlights-api.yaml @@ -3,6 +3,8 @@ kind: API metadata: name: streetlights description: The Smartylighting Streetlights API allows you to remotely manage the city lights. + tags: + - unstable spec: type: asyncapi definition: | diff --git a/packages/catalog-model/src/entity/Entity.ts b/packages/catalog-model/src/entity/Entity.ts index 4f245da8b7..7f323043b1 100644 --- a/packages/catalog-model/src/entity/Entity.ts +++ b/packages/catalog-model/src/entity/Entity.ts @@ -113,6 +113,12 @@ export type EntityMeta = JsonObject & { * entity. */ annotations?: Record; + + /** + * A list of single-valued strings, to for example classify catalog entities in + * various ways. + */ + tags?: string[]; }; /** diff --git a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts index 80cf70fbab..edf04a64cc 100644 --- a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts +++ b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts @@ -35,6 +35,9 @@ describe('FieldFormatEntityPolicy', () => { backstage.io/custom: ValueStuff annotations: example.com/bindings: are-secret + tags: + - java + - data-service spec: custom: stuff `); @@ -102,4 +105,9 @@ describe('FieldFormatEntityPolicy', () => { data.metadata.annotations.a = 7; await expect(policy.enforce(data)).rejects.toThrow(/annotation.*7/i); }); + + it('rejects bad tag value', async () => { + data.metadata.tags.push('Hello World'); + await expect(policy.enforce(data)).rejects.toThrow(/tags.*"Hello World"/i); + }); }); diff --git a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts index 4ccd8ec711..20a3724079 100644 --- a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts +++ b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts @@ -83,6 +83,12 @@ export class FieldFormatEntityPolicy implements EntityPolicy { require(`annotations.${k}`, v, this.validators.isValidAnnotationValue); } + const tags = entity.metadata.tags ?? []; + + for (let i = 0; i < tags.length; ++i) { + require(`tags.${i}`, tags[i], this.validators.isValidTag); + } + return entity; } } diff --git a/packages/catalog-model/src/entity/policies/ReservedFieldsEntityPolicy.test.ts b/packages/catalog-model/src/entity/policies/ReservedFieldsEntityPolicy.test.ts index 04acd58686..5d4e60a1a3 100644 --- a/packages/catalog-model/src/entity/policies/ReservedFieldsEntityPolicy.test.ts +++ b/packages/catalog-model/src/entity/policies/ReservedFieldsEntityPolicy.test.ts @@ -35,6 +35,9 @@ describe('ReservedFieldsEntityPolicy', () => { backstage.io/custom: ValueStuff annotations: example.com/bindings: are-secret + tags: + - java + - data spec: custom: stuff `); diff --git a/packages/catalog-model/src/entity/policies/ReservedFieldsEntityPolicy.ts b/packages/catalog-model/src/entity/policies/ReservedFieldsEntityPolicy.ts index bbe374e811..edb63cf05c 100644 --- a/packages/catalog-model/src/entity/policies/ReservedFieldsEntityPolicy.ts +++ b/packages/catalog-model/src/entity/policies/ReservedFieldsEntityPolicy.ts @@ -29,6 +29,7 @@ const DEFAULT_RESERVED_ENTITY_FIELDS: string[] = [ 'metadata.description', 'metadata.labels', 'metadata.annotations', + 'metadata.tags', // The below items are known to appear in core kinds, and therefore should // not be appearing in metadata (which would indicate that the user made a // mistake in where to place them). diff --git a/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.test.ts b/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.test.ts index 2113772af4..d63ba49dbe 100644 --- a/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.test.ts +++ b/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.test.ts @@ -36,6 +36,9 @@ describe('SchemaValidEntityPolicy', () => { backstage.io/custom: ValueStuff annotations: example.com/bindings: are-secret + tags: + - java + - data spec: custom: stuff `); @@ -190,6 +193,11 @@ describe('SchemaValidEntityPolicy', () => { await expect(policy.enforce(data)).rejects.toThrow(/annotations/); }); + it('rejects bad tags type', async () => { + data.metadata.tags = 7; + await expect(policy.enforce(data)).rejects.toThrow(/tags/); + }); + // // spec // diff --git a/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts b/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts index e901e82d8b..a6d376b4ac 100644 --- a/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts +++ b/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts @@ -31,6 +31,7 @@ const DEFAULT_ENTITY_SCHEMA = yup.object({ description: yup.string().notRequired(), labels: yup.object>().notRequired(), annotations: yup.object>().notRequired(), + tags: yup.array().notRequired(), }) .required(), spec: yup.object({}).notRequired(), diff --git a/packages/catalog-model/src/validation/makeValidator.ts b/packages/catalog-model/src/validation/makeValidator.ts index 7ca01365e0..3602c01a63 100644 --- a/packages/catalog-model/src/validation/makeValidator.ts +++ b/packages/catalog-model/src/validation/makeValidator.ts @@ -28,6 +28,7 @@ const defaultValidators: Validators = { isValidLabelValue: KubernetesValidatorFunctions.isValidLabelValue, isValidAnnotationKey: KubernetesValidatorFunctions.isValidAnnotationKey, isValidAnnotationValue: KubernetesValidatorFunctions.isValidAnnotationValue, + isValidTag: CommonValidatorFunctions.isValidDnsLabel, }; export function makeValidator(overrides: Partial = {}): Validators { diff --git a/packages/catalog-model/src/validation/types.ts b/packages/catalog-model/src/validation/types.ts index 81209bfb75..ff00036991 100644 --- a/packages/catalog-model/src/validation/types.ts +++ b/packages/catalog-model/src/validation/types.ts @@ -24,4 +24,5 @@ export type Validators = { isValidLabelValue(value: any): boolean; isValidAnnotationKey(value: any): boolean; isValidAnnotationValue(value: any): boolean; + isValidTag(value: any): boolean; }; diff --git a/packages/core/src/components/Table/Table.tsx b/packages/core/src/components/Table/Table.tsx index d200b07bc6..882272cb43 100644 --- a/packages/core/src/components/Table/Table.tsx +++ b/packages/core/src/components/Table/Table.tsx @@ -127,7 +127,8 @@ function convertColumns( ): TableColumn[] { return columns.map(column => { const headerStyle: React.CSSProperties = {}; - const cellStyle: React.CSSProperties = {}; + const cellStyle: React.CSSProperties = + typeof column.cellStyle === 'object' ? column.cellStyle : {}; if (column.highlight) { headerStyle.color = theme.palette.textContrast; diff --git a/packages/core/src/layout/Header/Header.tsx b/packages/core/src/layout/Header/Header.tsx index cc2c8d1182..7aafc2730e 100644 --- a/packages/core/src/layout/Header/Header.tsx +++ b/packages/core/src/layout/Header/Header.tsx @@ -14,59 +14,63 @@ * limitations under the License. */ -import React, { Fragment, ReactNode, CSSProperties, FC } from 'react'; +import React, { ReactNode, CSSProperties, FC, useContext } from 'react'; import { Helmet } from 'react-helmet'; import { Typography, Tooltip, makeStyles } from '@material-ui/core'; import { BackstageTheme } from '@backstage/theme'; -import { Theme } from '../Page/Page'; -import { Waves } from './Waves'; +import { PageThemeContext } from '../Page/Page'; -const useStyles = makeStyles(theme => ({ - header: { - gridArea: 'pageHeader', - padding: theme.spacing(3), - minHeight: 118, - width: '100%', - boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)', - position: 'relative', - zIndex: 100, - display: 'flex', - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'flex-end', - alignItems: 'center', - }, - leftItemsBox: { - flex: '1 1 auto', - }, - rightItemsBox: { - flex: '0 1 auto', - display: 'flex', - flexDirection: 'row', - flexWrap: 'wrap', - alignItems: 'center', - marginRight: theme.spacing(1), - }, - title: { - color: theme.palette.bursts.fontColor, - lineHeight: '1.0em', - wordBreak: 'break-all', - fontSize: 'calc(24px + 6 * ((100vw - 320px) / 680))', - marginBottom: theme.spacing(1), - }, - subtitle: { - color: 'rgba(255, 255, 255, 0.8)', - lineHeight: '1.0em', - }, - type: { - textTransform: 'uppercase', - fontSize: 11, - opacity: 0.8, - marginBottom: theme.spacing(1), - color: theme.palette.bursts.fontColor, - }, -})); +const useStyles = makeStyles( + theme => ({ + header: { + gridArea: 'pageHeader', + padding: theme.spacing(3), + minHeight: 118, + width: '100%', + boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)', + position: 'relative', + zIndex: 100, + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'flex-end', + alignItems: 'center', + backgroundImage: props => props.backgroundImage, + backgroundPosition: 'center', + backgroundSize: '100% 400px', + }, + leftItemsBox: { + flex: '1 1 auto', + }, + rightItemsBox: { + flex: '0 1 auto', + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap', + alignItems: 'center', + marginRight: theme.spacing(1), + }, + title: { + color: theme.palette.bursts.fontColor, + lineHeight: '1.0em', + wordBreak: 'break-all', + fontSize: 'calc(24px + 6 * ((100vw - 320px) / 680))', + marginBottom: theme.spacing(1), + }, + subtitle: { + color: 'rgba(255, 255, 255, 0.8)', + lineHeight: '1.0em', + }, + type: { + textTransform: 'uppercase', + fontSize: 11, + opacity: 0.8, + marginBottom: theme.spacing(1), + color: theme.palette.bursts.fontColor, + }, + }), +); type HeaderStyles = ReturnType; @@ -159,32 +163,28 @@ export const Header: FC = ({ type, typeLink, }) => { - const classes = useStyles(); + const theme = useContext(PageThemeContext); + const classes = useStyles({ backgroundImage: theme.backgroundImage }); const documentTitle = pageTitleOverride || title; const pageTitle = title || pageTitleOverride; const titleTemplate = `${documentTitle} | %s | Backstage`; const defaultTitle = `${documentTitle} | Backstage`; return ( - + <> - - {theme => ( -
- -
- - - -
-
{children}
-
- )} -
-
+
+
+ + + +
+
{children}
+
+ ); }; diff --git a/packages/core/src/layout/Header/Waves.test.tsx b/packages/core/src/layout/Header/Waves.test.tsx deleted file mode 100644 index 76bf5e913e..0000000000 --- a/packages/core/src/layout/Header/Waves.test.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { render } from '@testing-library/react'; -import { pageTheme } from '../Page/PageThemeProvider'; -import { Waves } from './Waves'; - -describe('', () => { - it('should render svg', () => { - const rendered = render(); - rendered.getByTestId('wave-svg'); - }); -}); diff --git a/packages/core/src/layout/Header/Waves.tsx b/packages/core/src/layout/Header/Waves.tsx deleted file mode 100644 index e87fc78105..0000000000 --- a/packages/core/src/layout/Header/Waves.tsx +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { FC } from 'react'; -import { makeStyles } from '@material-ui/core'; -import { PageTheme } from '../Page'; - -const useStyles = makeStyles({ - wave: { - position: 'absolute', - height: '100%', - width: '100%', - top: 0, - bottom: 0, - left: 0, - right: 0, - zIndex: -1, - }, -}); - -type Props = { - theme: PageTheme; -}; - -export const Waves: FC = ({ theme }) => { - const classes = useStyles(); - const [backgroundColor1, backgroundColor2] = theme.gradient.colors; - const waveColor = theme.gradient.waveColor; - const [opacityStart, opacityStop] = theme.gradient.opacity; - - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; diff --git a/packages/core/src/layout/Page/Page.tsx b/packages/core/src/layout/Page/Page.tsx index bc4175fb6c..a5f5e4fd2b 100644 --- a/packages/core/src/layout/Page/Page.tsx +++ b/packages/core/src/layout/Page/Page.tsx @@ -18,7 +18,7 @@ import React, { FC } from 'react'; import { PageTheme, pageTheme } from './PageThemeProvider'; import { makeStyles } from '@material-ui/core'; -export const Theme = React.createContext(pageTheme.home); +export const PageThemeContext = React.createContext(pageTheme.home); const useStyles = makeStyles(() => ({ root: { @@ -38,8 +38,8 @@ type Props = { export const Page: FC = ({ theme = pageTheme.home, children }) => { const classes = useStyles(); return ( - +
{children}
-
+ ); }; diff --git a/packages/core/src/layout/Page/PageThemeProvider.ts b/packages/core/src/layout/Page/PageThemeProvider.ts index c99509513f..57411c8786 100644 --- a/packages/core/src/layout/Page/PageThemeProvider.ts +++ b/packages/core/src/layout/Page/PageThemeProvider.ts @@ -14,87 +14,59 @@ * limitations under the License. */ -export type Gradient = { - colors: string[]; - waveColor: string; - opacity: string[]; -}; - export type PageTheme = { - gradient: Gradient; + colors: string[]; + shape: string; + backgroundImage: string; }; -export const gradients: Record = { - darkGrey: { - colors: ['#171717', '#383838'], - waveColor: '#757575', - opacity: ['1.0', '0.0'], - }, - marineBlue: { - colors: ['#00759A', '#004EAC'], - waveColor: '#BDDBFF', - opacity: ['0.72', '0.0'], - }, - veryBlue: { - colors: ['#0B2B9C', '#311288'], - waveColor: '#8960FD', - opacity: ['0.72', '0.0'], - }, - rubyRed: { - colors: ['#A4284B', '#8D1134'], - waveColor: '#FFBFF5', - opacity: ['0.28', '0.10'], - }, - toastyOrange: { - colors: ['#CC3707', '#9A2500'], - waveColor: '#FF784E', - opacity: ['0.72', '0.0'], - }, - purpleSky: { - colors: ['#AF29F8', '#4100F4'], - waveColor: '#AF29F8', - opacity: ['0.72', '0.0'], - }, - eveningSea: { - colors: ['#00FFF2', '#035355'], - waveColor: '', - opacity: ['0.72', '0.0'], - }, - teal: { - colors: ['#005E4D', '#004E40'], - waveColor: '#9BF0E1', - opacity: ['0.72', '0.0'], - }, - pinkSea: { - colors: ['#C8077A', '#C2297D'], - waveColor: '#ea93c3', - opacity: ['0.8', '0.0'], - }, +/* + # How to add a shape + 1. Get the svg shape from figma, should be ~1400 wide, ~400 high + and only the white->transparent mask, no colors. + 2. Run it through https://jakearchibald.github.io/svgomg/ + 3. Run that through https://github.com/tigt/mini-svg-data-uri + with something like https://npm.runkit.com/mini-svg-data-uri + 4. Wrap the output in `url("")` + 5. Give it a name and paste it into the `shapes` object below. + +*/ +export const shapes: Record = { + wave: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1368' height='400' fill='none'%3e%3cmask id='a' width='1368' height='401' x='0' y='0' maskUnits='userSpaceOnUse'%3e%3cpath fill='url(%23paint0_linear)' d='M437 116C223 116 112 0 112 0h1256v400c-82 0-225-21-282-109-112-175-436-175-649-175z'/%3e%3cpath fill='url(%23paint1_linear)' d='M1368 400V282C891-29 788 40 711 161 608 324 121 372 0 361v39h1368z'/%3e%3cpath fill='url(%23paint2_linear)' d='M1368 244v156H0V94c92-24 198-46 375 0l135 41c176 51 195 109 858 109z'/%3e%3cpath fill='url(%23paint3_linear)' d='M1252 400h116c-14-7-35-14-116-16-663-14-837-128-1013-258l-85-61C98 28 46 8 0 0v400h1252z'/%3e%3c/mask%3e%3cg mask='url(%23a)'%3e%3cpath fill='white' d='M-172-98h1671v601H-172z'/%3e%3c/g%3e%3cdefs%3e%3clinearGradient id='paint0_linear' x1='602' x2='1093.5' y1='-960.5' y2='272' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint1_linear' x1='482' x2='480' y1='1058.5' y2='70.5' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint2_linear' x1='424' x2='446.1' y1='-587.5' y2='274.6' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint3_linear' x1='587' x2='349' y1='-1120.5' y2='341' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e")`, + wave2: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1368' height='400' fill='none'%3e%3cmask id='a' width='1764' height='479' x='-229' y='-6' maskUnits='userSpaceOnUse'%3e%3cpath fill='url(%23paint0_linear)' d='M0 400h1350C1321 336 525 33 179-2c-345-34-395 236-408 402H0z'/%3e%3cpath fill='url(%23paint1_linear)' d='M1378 177v223H0V217s219 75 327 52C436 246 717-35 965 45s254 144 413 132z'/%3e%3cpath fill='url(%23paint2_linear)' d='M26 400l-78-16c-170 205-44-6-137-30l-4-1 4 1 137 30c37-45 89-110 159-201 399-514-45 238 1176-50 275-65 354-39 91 267H26z'/%3e%3c/mask%3e%3cg mask='url(%23a)'%3e%3cpath fill='white' d='M0 0h1368v400H0z'/%3e%3c/g%3e%3cdefs%3e%3clinearGradient id='paint0_linear' x1='431' x2='397.3' y1='-599' y2='372.8' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint1_linear' x1='236.5' x2='446.6' y1='-586' y2='381.5' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint2_linear' x1='851.8' x2='640.4' y1='-867.2' y2='363.7' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e")`, + round: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1368' height='400' fill='none'%3e%3cmask id='a' width='2269' height='1408' x='-610' y='-509' maskUnits='userSpaceOnUse'%3e%3ccircle cx='1212.8' cy='74.8' r='317.5' fill='url(%23paint0_linear)' transform='rotate(-52 1213 75)'/%3e%3ccircle cx='737.8' cy='445.8' r='317.5' fill='url(%23paint1_linear)' transform='rotate(-116 738 446)'/%3e%3ccircle cx='601.8' cy='52.8' r='418.6' fill='url(%23paint2_linear)' transform='rotate(-117 602 53)'/%3e%3ccircle cx='999.8' cy='364' r='389.1' fill='url(%23paint3_linear)' transform='rotate(31 1000 364)'/%3e%3cellipse cx='-109.2' cy='263.5' fill='url(%23paint4_linear)' rx='429.2' ry='465.8' transform='rotate(-85 -109 264)'/%3e%3c/mask%3e%3cg mask='url(%23a)'%3e%3cpath fill='white' d='M0 0h1368v400H0z'/%3e%3c/g%3e%3cdefs%3e%3clinearGradient id='paint0_linear' x1='1301.2' x2='161.4' y1='-1879.7' y2='-969.6' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint1_linear' x1='826.2' x2='-313.6' y1='-1508.7' y2='-598.6' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint2_linear' x1='718.4' x2='-784.3' y1='-2524' y2='-1324.2' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint3_linear' x1='1108.2' x2='-288.6' y1='-2031.1' y2='-915.9' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3clinearGradient id='paint4_linear' x1='10.4' x2='-1626.5' y1='-2603.8' y2='-1399.5' gradientUnits='userSpaceOnUse'%3e%3cstop stop-color='white'/%3e%3cstop offset='1' stop-color='white' stop-opacity='0'/%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e")`, +}; + +export const colorVariants: Record = { + darkGrey: ['#171717', '#383838'], + marineBlue: ['#006D8F', '#0049A1'], + veryBlue: ['#0027AF', '#270094'], + rubyRed: ['#98002B', '#8D1134'], + toastyOrange: ['#BE2200', '#A41D00'], + purpleSky: ['#8912CA', '#3E00EA'], + eveningSea: ['#00FFF2', '#035355'], + teal: ['#005B4B'], + pinkSea: ['#C8077A', '#C2297D'], }; export const pageTheme: Record = { - home: { - gradient: gradients.teal, - }, - documentation: { - gradient: gradients.pinkSea, - }, - tool: { - gradient: gradients.purpleSky, - }, - service: { - gradient: gradients.marineBlue, - }, - website: { - gradient: gradients.veryBlue, - }, - library: { - gradient: gradients.rubyRed, - }, - other: { - gradient: gradients.darkGrey, - }, - app: { - gradient: gradients.toastyOrange, - }, + home: genTheme(colorVariants.teal, shapes.wave), + documentation: genTheme(colorVariants.pinkSea, shapes.wave2), + tool: genTheme(colorVariants.purpleSky, shapes.round), + service: genTheme(colorVariants.marineBlue, shapes.wave), + website: genTheme(colorVariants.veryBlue, shapes.wave), + library: genTheme(colorVariants.rubyRed, shapes.wave), + other: genTheme(colorVariants.darkGrey, shapes.wave), + app: genTheme(colorVariants.toastyOrange, shapes.wave), }; + +// As the background shapes and colors are decorative, we place them onto +// the page as a css background-image instead of an html element of its own. +// Utility to not have to write colors and shapes twice. +function genTheme(colors: string[], shape: string) { + const gradientColors = colors.length === 1 ? [colors[0], colors[0]] : colors; + const gradient = `linear-gradient(90deg, ${gradientColors.join(', ')})`; + const backgroundImage = `${shape}, ${gradient}`; + + return { colors, shape, backgroundImage }; +} diff --git a/packages/core/src/layout/Page/index.ts b/packages/core/src/layout/Page/index.ts index f8fa7dc668..000a565c83 100644 --- a/packages/core/src/layout/Page/index.ts +++ b/packages/core/src/layout/Page/index.ts @@ -15,5 +15,5 @@ */ export { Page } from './Page'; -export { gradients, pageTheme } from './PageThemeProvider'; +export { pageTheme } from './PageThemeProvider'; export type { PageTheme } from './PageThemeProvider'; diff --git a/plugins/auth-backend/src/lib/EnvironmentHandler.ts b/plugins/auth-backend/src/lib/EnvironmentHandler.ts index fb39ed86b8..53e56a7420 100644 --- a/plugins/auth-backend/src/lib/EnvironmentHandler.ts +++ b/plugins/auth-backend/src/lib/EnvironmentHandler.ts @@ -19,6 +19,7 @@ import { AuthProviderRouteHandlers, EnvironmentIdentifierFn, } from '../providers/types'; +import { InputError } from '@backstage/backend-common'; export type EnvironmentHandlers = { [key: string]: AuthProviderRouteHandlers; @@ -37,7 +38,11 @@ export class EnvironmentHandler implements AuthProviderRouteHandlers { ): AuthProviderRouteHandlers | undefined { const env: string | undefined = this.envIdentifier(req); - if (env && this.providers.hasOwnProperty(env)) { + if (!env) { + throw new InputError(`Must specify 'env' query to select environment`); + } + + if (this.providers.hasOwnProperty(env)) { return this.providers[env]; } diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index e58b2d407b..6a3d7362aa 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -35,6 +35,7 @@ import { CatalogTable } from '../CatalogTable/CatalogTable'; import CatalogLayout from './CatalogLayout'; import { CatalogTabs, LabeledComponentType } from './CatalogTabs'; import { WelcomeBanner } from './WelcomeBanner'; +import { ResultsFilter } from '../ResultsFilter/ResultsFilter'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -47,7 +48,12 @@ const useStyles = makeStyles(theme => ({ const CatalogPageContents = () => { const styles = useStyles(); - const { loading, error, matchingEntities } = useFilteredEntities(); + const { + loading, + error, + matchingEntities, + availableTags, + } = useFilteredEntities(); const { isStarredEntity } = useStarredEntities(); const userId = useApi(identityApiRef).getUserId(); const [selectedTab, setSelectedTab] = useState(); @@ -140,6 +146,7 @@ const CatalogPageContents = () => { onChange={({ label }) => setSelectedSidebarItem(label)} initiallySelected="owned" /> +
[] = [ 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 = { diff --git a/plugins/catalog/src/components/EntityPage/EntityPage.tsx b/plugins/catalog/src/components/EntityPage/EntityPage.tsx index 5dd35bf8db..8a375b8d6e 100644 --- a/plugins/catalog/src/components/EntityPage/EntityPage.tsx +++ b/plugins/catalog/src/components/EntityPage/EntityPage.tsx @@ -27,22 +27,17 @@ import { Progress, useApi, } from '@backstage/core'; -import { SentryIssuesWidget } from '@backstage/plugin-sentry'; -import { Widget as GithubActionsWidget } from '@backstage/plugin-github-actions'; -import { - JenkinsBuildsWidget, - JenkinsLastBuildWidget, -} from '@backstage/plugin-jenkins'; -import { Grid, Box } from '@material-ui/core'; +import { Box } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import React, { FC, useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useAsync } from 'react-use'; import { catalogApiRef } from '../..'; import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu'; -import { EntityMetadataCard } from '../EntityMetadataCard/EntityMetadataCard'; -import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog'; +import { EntityPageApi } from '../EntityPageApi/EntityPageApi'; +import { EntityPageOverview } from '../EntityPageOverview/EntityPageOverview'; import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity'; +import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog'; const REDIRECT_DELAY = 1000; function headerProps( @@ -105,6 +100,8 @@ export const EntityPage: FC<{}> = () => { } }, [errorApi, navigate, error, loading, entity]); + const [selectedTabId, setSelectedTabId] = useState(''); + if (!name) { navigate('/catalog'); return null; @@ -122,6 +119,7 @@ export const EntityPage: FC<{}> = () => { { id: 'overview', label: 'Overview', + content: (e: Entity) => , }, { id: 'ci', @@ -134,6 +132,7 @@ export const EntityPage: FC<{}> = () => { { id: 'api', label: 'API', + content: (e: Entity) => , }, { id: 'monitoring', @@ -152,6 +151,8 @@ export const EntityPage: FC<{}> = () => { entity, ); + const selectedTab = tabs.find(tab => tab.id === selectedTabId) || tabs[0]; + return (
= () => { {entity && ( <> - + { + setSelectedTabId(tabs[idx].id); + }} + /> - - - - - {entity.metadata?.annotations?.[ - 'backstage.io/jenkins-github-folder' - ] && ( - - - - )} - {entity.metadata?.annotations?.[ - 'backstage.io/jenkins-github-folder' - ] && ( - - - - )} - {entity.metadata?.annotations?.[ - 'backstage.io/github-actions-id' - ] && ( - - - - )} - - - - + {selectedTab && selectedTab.content + ? selectedTab.content(entity) + : null} = ({ entity }) => { const { value: apiEntities, loading } = useAsync(async () => { const a = await Promise.all( - ((entity?.spec?.implementedApis as string[]) || []).map(api => + ((entity?.spec?.implementsApis as string[]) || []).map(api => catalogApi.getEntityByName({ kind: 'API', name: api, @@ -48,7 +48,7 @@ export const EntityPageApi: FC<{ entity: Entity }> = ({ entity }) => { {loading && } {!loading && ( - {((entity?.spec?.implementedApis as string[]) || []).map(api => { + {((entity?.spec?.implementsApis as string[]) || []).map(api => { const apiEntity = apiEntities && apiEntities.get(api); return ( diff --git a/plugins/catalog/src/components/EntityPageOverview/EntityPageOverview.tsx b/plugins/catalog/src/components/EntityPageOverview/EntityPageOverview.tsx index f4faa8c574..45a62198c7 100644 --- a/plugins/catalog/src/components/EntityPageOverview/EntityPageOverview.tsx +++ b/plugins/catalog/src/components/EntityPageOverview/EntityPageOverview.tsx @@ -18,7 +18,10 @@ import { Entity } from '@backstage/catalog-model'; import { Content } from '@backstage/core'; import { SentryIssuesWidget } from '@backstage/plugin-sentry'; import { Widget as GithubActionsWidget } from '@backstage/plugin-github-actions'; -import { JenkinsBuildsWidget, JenkinsLastBuildWidget, } from '@backstage/plugin-jenkins'; +import { + JenkinsBuildsWidget, + JenkinsLastBuildWidget, +} from '@backstage/plugin-jenkins'; import { Grid } from '@material-ui/core'; import React, { FC } from 'react'; import { EntityMetadataCard } from '../EntityMetadataCard/EntityMetadataCard'; @@ -32,14 +35,14 @@ export const EntityPageOverview: FC<{ entity: Entity }> = ({ entity }) => { {entity.metadata?.annotations?.[ 'backstage.io/jenkins-github-folder' - ] && ( + ] && ( )} {entity.metadata?.annotations?.[ 'backstage.io/jenkins-github-folder' - ] && ( + ] && ( @@ -49,12 +52,12 @@ export const EntityPageOverview: FC<{ entity: Entity }> = ({ entity }) => { )} - - - + + + ); diff --git a/plugins/catalog/src/components/ResultsFilter/ResultsFilter.test.tsx b/plugins/catalog/src/components/ResultsFilter/ResultsFilter.test.tsx new file mode 100644 index 0000000000..6f294c54ff --- /dev/null +++ b/plugins/catalog/src/components/ResultsFilter/ResultsFilter.test.tsx @@ -0,0 +1,102 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; +import { + ApiProvider, + ApiRegistry, + IdentityApi, + identityApiRef, + storageApiRef, +} from '@backstage/core'; +import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils'; +import { render } from '@testing-library/react'; +import React from 'react'; +import { CatalogApi, catalogApiRef } from '../../api/types'; +import { EntityFilterGroupsProvider } from '../../filter'; +import { ResultsFilter } from './ResultsFilter'; + +describe('Results Filter', () => { + const catalogApi: Partial = { + getEntities: () => + Promise.resolve([ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'Entity1', + tags: ['java'], + }, + spec: { + owner: 'tools@example.com', + type: 'service', + }, + }, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'Entity2', + }, + spec: { + owner: 'not-tools@example.com', + type: 'service', + }, + }, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'Entity3', + tags: ['java', 'test'], + }, + spec: { + owner: 'tools@example.com', + type: 'service', + }, + }, + ] as Entity[]), + }; + + const indentityApi: Partial = { + getUserId: () => 'tools@example.com', + }; + + const renderWrapped = (children: React.ReactNode) => + render( + wrapInTestApp( + + {children}, + , + ), + ); + + it('should render all available tags', async () => { + const tags = ['test', 'java']; + const { findByText } = renderWrapped( + , + ); + for (const tag of tags) { + expect(await findByText(tag)).toBeInTheDocument(); + } + }); +}); diff --git a/plugins/catalog/src/components/ResultsFilter/ResultsFilter.tsx b/plugins/catalog/src/components/ResultsFilter/ResultsFilter.tsx new file mode 100644 index 0000000000..eb500da651 --- /dev/null +++ b/plugins/catalog/src/components/ResultsFilter/ResultsFilter.tsx @@ -0,0 +1,120 @@ +/* + * 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 { + Button, + Checkbox, + Divider, + List, + ListItem, + ListItemText, + makeStyles, + Theme, + Typography, +} from '@material-ui/core'; +import React, { useCallback, useContext, useState } from 'react'; +import { filterGroupsContext } from '../../filter/context'; + +const useStyles = makeStyles(theme => ({ + filterBox: { + display: 'flex', + margin: theme.spacing(2, 0, 0, 0), + }, + filterBoxTitle: { + margin: theme.spacing(1, 0, 0, 1), + fontWeight: 'bold', + flex: 1, + }, + title: { + margin: theme.spacing(1, 0, 0, 1), + textTransform: 'uppercase', + fontSize: 12, + fontWeight: 'bold', + }, + checkbox: { + padding: theme.spacing(0, 1, 0, 1), + }, +})); + +type Props = { + availableTags: string[]; +}; + +/** + * The additional results filter in the sidebar. + */ +export const ResultsFilter = ({ availableTags }: Props) => { + const classes = useStyles(); + + const [selectedTags, setSelectedTags] = useState([]); + const context = useContext(filterGroupsContext); + if (!context) { + throw new Error(`Must be used inside an EntityFilterGroupsProvider`); + } + const setSelectedTagsFilter = context?.setSelectedTags; + + const updateSelectedTags = useCallback( + (tags: string[]) => { + setSelectedTags(tags); + setSelectedTagsFilter(tags); + }, + [setSelectedTags, setSelectedTagsFilter], + ); + + return ( + <> +
+ + Refine Results + {' '} + +
+ + + Tags + + + {availableTags.map(t => { + const labelId = `checkbox-list-label-${t}`; + return ( + + updateSelectedTags( + selectedTags.includes(t) + ? selectedTags.filter(s => s !== t) + : [...selectedTags, t], + ) + } + > + + + + ); + })} + + + ); +}; diff --git a/plugins/catalog/src/filter/EntityFilterGroupsProvider.tsx b/plugins/catalog/src/filter/EntityFilterGroupsProvider.tsx index dd557a4322..db8e21e071 100644 --- a/plugins/catalog/src/filter/EntityFilterGroupsProvider.tsx +++ b/plugins/catalog/src/filter/EntityFilterGroupsProvider.tsx @@ -54,16 +54,19 @@ function useProvideEntityFilters(): FilterGroupsContext { const selectedFilterKeys = useRef<{ [filterGroupId: string]: Set; }>({}); + const selectedTags = useRef([]); const [filterGroupStates, setFilterGroupStates] = useState<{ [filterGroupId: string]: FilterGroupStates; }>({}); const [matchingEntities, setMatchingEntities] = useState([]); + const [availableTags, setAvailableTags] = useState([]); const rebuild = useCallback(() => { setFilterGroupStates( buildStates( filterGroups.current, selectedFilterKeys.current, + selectedTags.current, entities, error, ), @@ -72,9 +75,11 @@ function useProvideEntityFilters(): FilterGroupsContext { buildMatchingEntities( filterGroups.current, selectedFilterKeys.current, + selectedTags.current, entities, ), ); + setAvailableTags(collectTags(entities)); }, [entities, error]); const register = useCallback( @@ -109,14 +114,24 @@ function useProvideEntityFilters(): FilterGroupsContext { [rebuild], ); + const setSelectedTags = useCallback( + (tags: string[]) => { + selectedTags.current = tags; + rebuild(); + }, + [rebuild], + ); + return { register, unregister, setGroupSelectedFilters, + setSelectedTags, loading: !error && !entities, error, filterGroupStates, matchingEntities, + availableTags, }; } @@ -125,6 +140,7 @@ function useProvideEntityFilters(): FilterGroupsContext { function buildStates( filterGroups: { [filterGroupId: string]: FilterGroup }, selectedFilterKeys: { [filterGroupId: string]: Set }, + selectedTags: string[], entities?: Entity[], error?: Error, ): { [filterGroupId: string]: FilterGroupStates } { @@ -153,6 +169,7 @@ function buildStates( const otherMatchingEntities = buildMatchingEntities( filterGroups, selectedFilterKeys, + selectedTags, entities, filterGroupId, ); @@ -170,11 +187,23 @@ function buildStates( return result; } +// Given all entites, find all possible tags and provide them in a sorted list. +function collectTags(entities?: Entity[]): string[] { + const tags = new Set(); + (entities || []).forEach(e => { + if (e.metadata.tags) { + e.metadata.tags.forEach(t => tags.add(t)); + } + }); + return Array.from(tags).sort(); +} + // Given all filter groups and what filters are actually selected, extract all // entities that match all those filter groups. function buildMatchingEntities( filterGroups: { [filterGroupId: string]: FilterGroup }, selectedFilterKeys: { [filterGroupId: string]: Set }, + selectedTags: string[], entities?: Entity[], excludeFilterGroupId?: string, ): Entity[] { @@ -201,6 +230,16 @@ function buildMatchingEntities( } } + // Filter by tags, if at least one tag is selected. Include all entities + // that have at least one of the selected tags + if (selectedTags.length > 0) { + allFilters.push( + entity => + !!entity.metadata.tags && + entity.metadata.tags.some(t => selectedTags.includes(t)), + ); + } + // All filter groups that had any checked filters need to match. Note that // every() always returns true for an empty array. return entities?.filter(entity => allFilters.every(fn => fn(entity))) ?? []; diff --git a/plugins/catalog/src/filter/context.ts b/plugins/catalog/src/filter/context.ts index 31e9c91b97..7f16107f09 100644 --- a/plugins/catalog/src/filter/context.ts +++ b/plugins/catalog/src/filter/context.ts @@ -26,10 +26,12 @@ export type FilterGroupsContext = { ) => void; unregister: (filterGroupId: string) => void; setGroupSelectedFilters: (filterGroupId: string, filterIds: string[]) => void; + setSelectedTags: (tags: string[]) => void; loading: boolean; error?: Error; filterGroupStates: { [filterGroupId: string]: FilterGroupStates }; matchingEntities: Entity[]; + availableTags: string[]; }; /** diff --git a/plugins/catalog/src/filter/useFilteredEntities.ts b/plugins/catalog/src/filter/useFilteredEntities.ts index 256f5247c7..3f70c0bd85 100644 --- a/plugins/catalog/src/filter/useFilteredEntities.ts +++ b/plugins/catalog/src/filter/useFilteredEntities.ts @@ -30,5 +30,6 @@ export function useFilteredEntities() { loading: context.loading, error: context.error, matchingEntities: context.matchingEntities, + availableTags: context.availableTags, }; } diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index a034c14a11..27437d0318 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -49,7 +49,7 @@ "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", - "@types/codemirror": "^0.0.96", + "@types/codemirror": "^0.0.97", "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "jest-fetch-mock": "^3.0.3", diff --git a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx index 7d38c6f223..d98b1a11e9 100644 --- a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx @@ -56,7 +56,7 @@ export const TemplateCard = ({ name, }: TemplateCardProps) => { const theme = pageTheme[type] ?? pageTheme.other; - const [gradientStart, gradientStop] = theme.gradient.colors; + const [gradientStart, gradientStop] = theme.colors; const classes = useStyles({ gradientStart, gradientStop }); const href = generatePath(templateRoute.path, { templateName: name }); diff --git a/yarn.lock b/yarn.lock index 659785ba35..50a5cc7887 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1469,10 +1469,10 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c" - integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.10.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.11.2" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736" + integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw== dependencies: regenerator-runtime "^0.13.4" @@ -3671,9 +3671,9 @@ shortid "^2.2.14" "@rjsf/material-ui@^2.1.0": - version "2.1.0" - resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-2.1.0.tgz#a361b125af3a383b7f671634b8d254345f9f9311" - integrity sha512-9mMttnPNP6GiP7BtZGimYcYsbWwjyviqg/PD8oxrkEtZykULXOIdC3WDMJ3nPSym8RvZsgSnB8bajCpa5iSYQQ== + version "2.3.0" + resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-2.3.0.tgz#a051eb4db2ad778e39933a31ba806f415dd3ba90" + integrity sha512-v/xZ4Xk18ZgBARcCe99IDqdO97GU0UC784gG8PhGGFDdy5Rbdy7ixTjHkGYttkr43PB7SX6ttohNhkKSW4nATQ== "@roadiehq/backstage-plugin-travis-ci@^0.1.3": version "0.1.3" @@ -3903,16 +3903,16 @@ util-deprecate "^1.0.2" "@storybook/addons@^6.0.4": - version "6.0.4" - resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.0.4.tgz#ee240b77a2c33dfabe4415a4a23c6937bc31c8aa" - integrity sha512-WBkhS2F2gPCqH6SWYaaBgR0xDZ19vrLExfYp1ULof2KMtsKKPQqR6bGF0D5ijLXonea/aemIc50GMhVtLR2qfg== + version "6.0.12" + resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.0.12.tgz#f57f89e0aa55febfb5437ddc2628a0ccc9f44f44" + integrity sha512-gVCyWK4jys5cUY0d3/Bxi02oeCsgdi6xVvA+T4v+SgeduAfm/k01tdO2qDXL37Sl+2TT9HBQGazDrsIUW4d7Ug== dependencies: - "@storybook/api" "6.0.4" - "@storybook/channels" "6.0.4" - "@storybook/client-logger" "6.0.4" - "@storybook/core-events" "6.0.4" - "@storybook/router" "6.0.4" - "@storybook/theming" "6.0.4" + "@storybook/api" "6.0.12" + "@storybook/channels" "6.0.12" + "@storybook/client-logger" "6.0.12" + "@storybook/core-events" "6.0.12" + "@storybook/router" "6.0.12" + "@storybook/theming" "6.0.12" core-js "^3.0.1" global "^4.3.2" regenerator-runtime "^0.13.3" @@ -3943,19 +3943,19 @@ telejson "^3.2.0" util-deprecate "^1.0.2" -"@storybook/api@6.0.4": - version "6.0.4" - resolved "https://registry.npmjs.org/@storybook/api/-/api-6.0.4.tgz#bf62c1a0092066a4836c1cf516f7780b13e03f37" - integrity sha512-hQuctJcYAGk946B4Qw/TPeYGDhou0fCVSjmrjakaFIQlaExAuq5gZARBPE/iPIvuVNEfIJUz5R8fbMJtJ5NH7w== +"@storybook/api@6.0.12": + version "6.0.12" + resolved "https://registry.npmjs.org/@storybook/api/-/api-6.0.12.tgz#d6ded5c9ac8f989c4915e11a80a4db69341fc95f" + integrity sha512-8+jPtfhUVM1hT22OT4rjHRxkW924gbWrAxCFYUXOw80a0x7BcT4sL2ah1D4FWf0IpCT/onLf9jLvSVXr8V0xOw== dependencies: "@reach/router" "^1.3.3" - "@storybook/channels" "6.0.4" - "@storybook/client-logger" "6.0.4" - "@storybook/core-events" "6.0.4" + "@storybook/channels" "6.0.12" + "@storybook/client-logger" "6.0.12" + "@storybook/core-events" "6.0.12" "@storybook/csf" "0.0.1" - "@storybook/router" "6.0.4" + "@storybook/router" "6.0.12" "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.0.4" + "@storybook/theming" "6.0.12" "@types/reach__router" "^1.3.5" core-js "^3.0.1" fast-deep-equal "^3.1.1" @@ -3987,10 +3987,10 @@ dependencies: core-js "^3.0.1" -"@storybook/channels@6.0.4": - version "6.0.4" - resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.0.4.tgz#c47766961a8bad61db706cfdda5511c741aa215a" - integrity sha512-8yOc9NFbtrw+30wCHABgobRVyBnDnQcueLH4KYWHKwlgK3IKBFF6g0dnlTgi9HSwkK1B1Rqmt00oJKnwZmNYCw== +"@storybook/channels@6.0.12": + version "6.0.12" + resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.0.12.tgz#377f8049272f1862f9235a5051d95124d53fa08e" + integrity sha512-0EMtjde4tRrBnJj5jOXSgtMYfMxGZgoe/0hvVSJuOABf0FY5x6xrqNNDfory7+TtgieuoQE4idl2/tdHE6QJJA== dependencies: core-js "^3.0.1" ts-dedent "^1.1.1" @@ -4026,10 +4026,10 @@ dependencies: core-js "^3.0.1" -"@storybook/client-logger@6.0.4": - version "6.0.4" - resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.0.4.tgz#85e82a68c0456dd829852ef4ef92d2f055b08ed8" - integrity sha512-fNw5iIptcNZBmazDc+JdwXX63UATg+/1tbSoVy8N/ntkh7Ttb21QmvurdHVtElipQIJEt+e7NYCw3x7uM49FFw== +"@storybook/client-logger@6.0.12": + version "6.0.12" + resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.0.12.tgz#33b4c3cd1f1d98dab32d8c8c906301f1ab18f969" + integrity sha512-MEFDlBbbqcivF/Xmxitx/ky8kxN7TVBZ7K754/pPEI5q6UW32DecJIRg79UWp/1nBPMX/A0U3ORwv+0MjgDZBQ== dependencies: core-js "^3.0.1" global "^4.3.2" @@ -4068,10 +4068,10 @@ dependencies: core-js "^3.0.1" -"@storybook/core-events@6.0.4": - version "6.0.4" - resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.0.4.tgz#6ecde97cc5d0a0dd3e1b4c2358586998ae81d8f4" - integrity sha512-iMLPVIioD1/CUchm3NUB4ou8IchT+E36YSFZnlitof6HJKZbdpq+cFdVXrPGm/OdwUokgx63tWob3t0QaVRMcg== +"@storybook/core-events@6.0.12": + version "6.0.12" + resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.0.12.tgz#499ae06092103b149fcd9417d8e0baa356adc2c0" + integrity sha512-52yNnp+dBkHiG9S+rQO7Nv3PdSDi0XnBt7FoQ+v8H31vGpgdBLEhy8w5ZA4eTrL951VaU/4/XoOaG2+yPALaoA== dependencies: core-js "^3.0.1" @@ -4215,10 +4215,10 @@ qs "^6.6.0" util-deprecate "^1.0.2" -"@storybook/router@6.0.4": - version "6.0.4" - resolved "https://registry.npmjs.org/@storybook/router/-/router-6.0.4.tgz#8b9727dd8a966129311615288174e03131792d0f" - integrity sha512-+XFRbRuEWhGKemc2ihIxGVMlQ4kyQ0Al1E9Lx7gDdiq6BjClhEsxmxHb/dqpcSlIw4QCVcINj9zZ6htur0w25w== +"@storybook/router@6.0.12": + version "6.0.12" + resolved "https://registry.npmjs.org/@storybook/router/-/router-6.0.12.tgz#f66d979ec01e15c11a378eb5dde3e696747ac184" + integrity sha512-vv1jHOOGelSzmDJnp9SdC/KR5RpE2am568ImOAQ9/XCmXNDhVshVlIS7ajy6yCKN/mS/63zKflbRNef+3SLU9Q== dependencies: "@reach/router" "^1.3.3" "@types/reach__router" "^1.3.5" @@ -4269,15 +4269,15 @@ resolve-from "^5.0.0" ts-dedent "^1.1.0" -"@storybook/theming@6.0.4": - version "6.0.4" - resolved "https://registry.npmjs.org/@storybook/theming/-/theming-6.0.4.tgz#cfcb77fff483a8963b0a2033b14dd1c3588412ae" - integrity sha512-iMH5jh28yp9fvI3hgnAvauGEOUj1ZerwAlbYGyrOY/qlAirfso9jX7Fp5x2gpvshFIjCgLXLeOXH+zLZJ8gBmg== +"@storybook/theming@6.0.12": + version "6.0.12" + resolved "https://registry.npmjs.org/@storybook/theming/-/theming-6.0.12.tgz#83b2099e7a7a5bd3acadb8e4106234ede62197c7" + integrity sha512-hmF6EIbm2A7G84+JR36UQWteElSwSNfGLzccAlUMiZIhdMG0SuCtyHe6FmckAWC226Mv+MW14fr+a4+OuRpM4g== dependencies: "@emotion/core" "^10.0.20" "@emotion/is-prop-valid" "^0.8.6" "@emotion/styled" "^10.0.17" - "@storybook/client-logger" "6.0.4" + "@storybook/client-logger" "6.0.12" core-js "^3.0.1" deep-object-diff "^1.1.0" emotion-theming "^10.0.19" @@ -4835,10 +4835,10 @@ dependencies: "@types/node" "*" -"@types/codemirror@^0.0.96": - version "0.0.96" - resolved "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.96.tgz#73b52e784a246cebef31d544fef45ea764de5bad" - integrity sha512-GTswEV26Bl1byRxpD3sKd1rT2AISr0rK9ImlJgEzfvqhcVWeu4xQKFQI6UgSC95NT5swNG4st/oRMeGVZgPj9w== +"@types/codemirror@^0.0.97": + version "0.0.97" + resolved "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.97.tgz#6f2d8266b7f1b34aacfe8c77221fafe324c3d081" + integrity sha512-n5d7o9nWhC49DjfhsxANP7naWSeTzrjXASkUDQh7626sM4zK9XP2EVcHp1IcCf/IPV6c7ORzDUDF3Bkt231VKg== dependencies: "@types/tern" "*" @@ -5724,9 +5724,9 @@ source-map "^0.6.1" "@types/webpack@*", "@types/webpack@^4.41.7", "@types/webpack@^4.41.8": - version "4.41.17" - resolved "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.17.tgz#0a69005e644d657c85b7d6ec1c826a71bebd1c93" - integrity sha512-6FfeCidTSHozwKI67gIVQQ5Mp0g4X96c2IXxX75hYEQJwST/i6NyZexP//zzMOBb+wG9jJ7oO8fk9yObP2HWAw== + version "4.41.21" + resolved "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.21.tgz#cc685b332c33f153bb2f5fc1fa3ac8adeb592dee" + integrity sha512-2j9WVnNrr/8PLAB5csW44xzQSJwS26aOnICsP3pSGCEdsu6KYtfQ6QJsVUKHWRnm1bL7HziJsfh5fHqth87yKA== dependencies: "@types/anymatch" "*" "@types/node" "*" @@ -8498,12 +8498,7 @@ color@^3.0.0, color@^3.1.2: color-convert "^1.9.1" color-string "^1.5.2" -colorette@1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/colorette/-/colorette-1.1.0.tgz#1f943e5a357fac10b4e0f5aaef3b14cdc1af6ec7" - integrity sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg== - -colorette@^1.2.1: +colorette@1.2.1, colorette@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== @@ -8643,7 +8638,7 @@ concat-stream@^1.5.0, concat-stream@^1.5.2, concat-stream@^1.6.2: readable-stream "^2.2.2" typedarray "^0.0.6" -concat-stream@^2.0.0, concat-stream@~2.0.0: +concat-stream@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== @@ -9978,16 +9973,7 @@ docker-modem@^2.1.0: split-ca "^1.0.1" ssh2 "^0.8.7" -dockerode@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/dockerode/-/dockerode-3.2.0.tgz#80e5bd9c2a988bdde4c995ae4aa2584fa0166c23" - integrity sha512-C+y/W4Kks7YLBsfUOTMkk1IVilb4cdj+rE+UZ5hnE+rpcn2frSs7kX+6H8GteTqHcv8sln+GyxuP1qdno3IzIw== - dependencies: - concat-stream "~2.0.0" - docker-modem "^2.1.0" - tar-fs "~2.0.1" - -dockerode@^3.2.1: +dockerode@^3.2.0, dockerode@^3.2.1: version "3.2.1" resolved "https://registry.npmjs.org/dockerode/-/dockerode-3.2.1.tgz#4a2222e3e1df536bf595e78e76d3cfbf6d4d93b9" integrity sha512-XsSVB5Wu5HWMg1aelV5hFSqFJaKS5x1aiV/+sT7YOzOq1IRl49I/UwV8Pe4x6t0iF9kiGkWu5jwfvbkcFVupBw== @@ -13595,10 +13581,10 @@ interpret@^1.0.0: resolved "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== -interpret@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/interpret/-/interpret-2.0.0.tgz#b783ffac0b8371503e9ab39561df223286aa5433" - integrity sha512-e0/LknJ8wpMMhTiWcjivB+ESwIuvHnBSlBbmP/pSb8CQJldoj1p2qv7xGZ/+BtbTziYRFSz8OsvdbiX45LtYQA== +interpret@^2.0.0, interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== into-stream@^3.1.0: version "3.1.0" @@ -15287,25 +15273,25 @@ kleur@^3.0.3: integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== knex@^0.21.1: - version "0.21.1" - resolved "https://registry.npmjs.org/knex/-/knex-0.21.1.tgz#4fba7e6c58c9f459846c3090be157a732fc75e41" - integrity sha512-uWszXC2DPaLn/YznGT9wFTWUG9+kqbL4DMz+hCH789GLcLuYzq8werHPDKBJxtKvxrW/S1XIXgrTWdMypiVvsw== + version "0.21.5" + resolved "https://registry.npmjs.org/knex/-/knex-0.21.5.tgz#c4be1958488f348aed3510aa4b7115639ee1bd01" + integrity sha512-cQj7F2D/fu03eTr6ZzYCYKdB9w7fPYlvTiU/f2OeXay52Pq5PwD+NAkcf40WDnppt/4/4KukROwlMOaE7WArcA== dependencies: - colorette "1.1.0" + colorette "1.2.1" commander "^5.1.0" debug "4.1.1" esm "^3.2.25" getopts "2.2.5" inherits "~2.0.4" - interpret "^2.0.0" + interpret "^2.2.0" liftoff "3.1.0" - lodash "^4.17.15" + lodash "^4.17.20" mkdirp "^1.0.4" - pg-connection-string "2.2.0" + pg-connection-string "2.3.0" tarn "^3.0.0" tildify "2.0.0" uuid "^7.0.3" - v8flags "^3.1.3" + v8flags "^3.2.0" kuler@1.0.x: version "1.0.1" @@ -15835,7 +15821,7 @@ lodash@4.17.15: resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== -lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4, lodash@^4.2.1, lodash@~4.17.10, lodash@~4.17.15: +lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.1, lodash@~4.17.10, lodash@~4.17.15: version "4.17.20" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -18220,12 +18206,7 @@ pg-connection-string@0.1.3, pg-connection-string@^0.1.3: resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7" integrity sha1-2hhHsglA5C7hSSvq9l1J2RskXfc= -pg-connection-string@2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.2.0.tgz#caab4d38a9de4fdc29c9317acceed752897de41c" - integrity sha512-xB/+wxcpFipUZOQcSzcgkjcNOosGhEoPSjz06jC89lv1dj7mc9bZv6wLVy8M2fVjP0a/xN0N988YDq1L0FhK3A== - -pg-connection-string@^2.3.0: +pg-connection-string@2.3.0, pg-connection-string@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.3.0.tgz#c13fcb84c298d0bfa9ba12b40dd6c23d946f55d6" integrity sha512-ukMTJXLI7/hZIwTW7hGMZJ0Lj0S2XQBCJ4Shv4y1zgQ/vqVea+FLhzywvPj0ujSuofu+yA4MYHGZPTsgjBgJ+w== @@ -22309,10 +22290,10 @@ symbol.prototype.description@^1.0.0: es-abstract "^1.17.0-next.1" has-symbols "^1.0.1" -synchronous-promise@^2.0.10: - version "2.0.11" - resolved "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.11.tgz#e92022b0754e916f556d3ace1626d24a24214b7e" - integrity sha512-8/L5FOCjnlK0FoAfj+NqdCaImMKvEyOEzGmdfcezKp5K9HIukm4akx72endvM87eS/gU8kOxiMQflpC/CdkQAg== +synchronous-promise@^2.0.13: + version "2.0.13" + resolved "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.13.tgz#9d8c165ddee69c5a6542862b405bc50095926702" + integrity sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA== table@^5.2.3: version "5.4.6" @@ -23531,10 +23512,10 @@ v8-to-istanbul@^4.1.3: convert-source-map "^1.6.0" source-map "^0.7.3" -v8flags@^3.1.3: - version "3.1.3" - resolved "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8" - integrity sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w== +v8flags@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz#b243e3b4dfd731fa774e7492128109a0fe66d656" + integrity sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg== dependencies: homedir-polyfill "^1.0.1" @@ -24480,16 +24461,16 @@ yn@^4.0.0: integrity sha512-huWiiCS4TxKc4SfgmTwW1K7JmXPPAmuXWYy4j9qjQo4+27Kni8mGhAAi1cloRWmBe2EqcLgt3IGqQoRL/MtPgg== yup@^0.29.1: - version "0.29.1" - resolved "https://registry.npmjs.org/yup/-/yup-0.29.1.tgz#35d25aab470a0c3950f66040ba0ff4b1b6efe0d9" - integrity sha512-U7mPIbgfQWI6M3hZCJdGFrr+U0laG28FxMAKIgNvgl7OtyYuUoc4uy9qCWYHZjh49b8T7Ug8NNDdiMIEytcXrQ== + version "0.29.3" + resolved "https://registry.npmjs.org/yup/-/yup-0.29.3.tgz#69a30fd3f1c19f5d9e31b1cf1c2b851ce8045fea" + integrity sha512-RNUGiZ/sQ37CkhzKFoedkeMfJM0vNQyaz+wRZJzxdKE7VfDeVKH8bb4rr7XhRLbHJz5hSjoDNwMEIaKhuMZ8gQ== dependencies: - "@babel/runtime" "^7.9.6" + "@babel/runtime" "^7.10.5" fn-name "~3.0.0" lodash "^4.17.15" lodash-es "^4.17.11" property-expr "^2.0.2" - synchronous-promise "^2.0.10" + synchronous-promise "^2.0.13" toposort "^2.0.2" zen-observable-ts@^0.8.21: