Merge branch 'master' into orkohunter/techdocs-publish-to-cloud-storage

This commit is contained in:
Himanshu Mishra
2020-12-09 20:17:06 +01:00
committed by GitHub
145 changed files with 3540 additions and 1115 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-jenkins': patch
---
Avoid loading data from Jenkins twice. Don't load data when navigating through the pages as all data from all pages is already loaded.
+11
View File
@@ -0,0 +1,11 @@
---
'@backstage/plugin-jenkins': patch
---
Improve loading speed of the CI/CD page.
Only request the necessary fields from Jenkins to keep the request size low.
In addition everything is loaded in a single request, instead of requesting
each job and build individually. As this (and also the previous behavior) can
lead to a big amount of data, this limits the amount of jobs to 50.
For each job, only the latest build is loaded. Loading the full build history
of a job can lead to excessive load on the Jenkins instance.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core': patch
---
Fix React warning of descendant paragraph tag
+13
View File
@@ -0,0 +1,13 @@
---
'@backstage/plugin-api-docs': minor
---
Stop exposing a custom router from the `api-docs` plugin. Instead, use the
widgets exported by the plugin to compose your custom entity pages.
Instead of displaying the API definitions directly in the API tab of the
component, it now contains tables linking to the API entities. This also adds
new widgets to display relationships (bot provides & consumes relationships)
between components and APIs.
See the changelog of `create-app` for a migration guide.
+136
View File
@@ -0,0 +1,136 @@
---
'@backstage/create-app': patch
---
Adjust template to the latest changes in the `api-docs` plugin.
## Template Changes
While updating to the latest `api-docs` plugin, the following changes are
necessary for the `create-app` template in your
`app/src/components/catalog/EntityPage.tsx`. This adds:
- A custom entity page for API entities
- Changes the API tab to include the new `ConsumedApisCard` and
`ProvidedApisCard` that link to the API entity.
```diff
import {
+ ApiDefinitionCard,
- Router as ApiDocsRouter,
+ ConsumedApisCard,
+ ProvidedApisCard,
+ ConsumedApisCard,
+ ConsumingComponentsCard,
+ ProvidedApisCard,
+ ProvidingComponentsCard
} from '@backstage/plugin-api-docs';
...
+const ComponentApisContent = ({ entity }: { entity: Entity }) => (
+ <Grid container spacing={3} alignItems="stretch">
+ <Grid item md={6}>
+ <ProvidedApisCard entity={entity} />
+ </Grid>
+ <Grid item md={6}>
+ <ConsumedApisCard entity={entity} />
+ </Grid>
+ </Grid>
+);
const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout>
<EntityPageLayout.Content
path="/"
title="Overview"
element={<OverviewContent entity={entity} />}
/>
<EntityPageLayout.Content
path="/ci-cd/*"
title="CI/CD"
element={<CICDSwitcher entity={entity} />}
/>
<EntityPageLayout.Content
path="/api/*"
title="API"
- element={<ApiDocsRouter entity={entity} />}
+ element={<ComponentApisContent entity={entity} />}
/>
...
-export const EntityPage = () => {
- const { entity } = useEntity();
- switch (entity?.spec?.type) {
- case 'service':
- return <ServiceEntityPage entity={entity} />;
- case 'website':
- return <WebsiteEntityPage entity={entity} />;
- default:
- return <DefaultEntityPage entity={entity} />;
- }
-};
+export const ComponentEntityPage = ({ entity }: { entity: Entity }) => {
+ switch (entity?.spec?.type) {
+ case 'service':
+ return <ServiceEntityPage entity={entity} />;
+ case 'website':
+ return <WebsiteEntityPage entity={entity} />;
+ default:
+ return <DefaultEntityPage entity={entity} />;
+ }
+};
+
+const ApiOverviewContent = ({ entity }: { entity: Entity }) => (
+ <Grid container spacing={3}>
+ <Grid item md={6}>
+ <AboutCard entity={entity} />
+ </Grid>
+ <Grid container item md={12}>
+ <Grid item md={6}>
+ <ProvidingComponentsCard entity={entity} />
+ </Grid>
+ <Grid item md={6}>
+ <ConsumingComponentsCard entity={entity} />
+ </Grid>
+ </Grid>
+ </Grid>
+);
+
+const ApiDefinitionContent = ({ entity }: { entity: ApiEntity }) => (
+ <Grid container spacing={3}>
+ <Grid item xs={12}>
+ <ApiDefinitionCard apiEntity={entity} />
+ </Grid>
+ </Grid>
+);
+
+const ApiEntityPage = ({ entity }: { entity: Entity }) => (
+ <EntityPageLayout>
+ <EntityPageLayout.Content
+ path="/*"
+ title="Overview"
+ element={<ApiOverviewContent entity={entity} />}
+ />
+ <EntityPageLayout.Content
+ path="/definition/*"
+ title="Definition"
+ element={<ApiDefinitionContent entity={entity as ApiEntity} />}
+ />
+ </EntityPageLayout>
+);
+
+export const EntityPage = () => {
+ const { entity } = useEntity();
+
+ switch (entity?.kind?.toLowerCase()) {
+ case 'component':
+ return <ComponentEntityPage entity={entity} />;
+ case 'api':
+ return <ApiEntityPage entity={entity} />;
+ default:
+ return <DefaultEntityPage entity={entity} />;
+ }
+};
```
+2 -2
View File
@@ -68,9 +68,9 @@ Firekube
freben
Fredrik
github
Github
GitHub
gitlab
Gitlab
GitLab
Grafana
graphql
graphviz
+1 -1
View File
@@ -62,7 +62,7 @@ jobs:
- name: prepare nightly release
run: yarn changeset version --snapshot nightly
# Publishes the nightly release to NPM, by using tag we make sure the release is
# Publishes the nightly release to npm, by using tag we make sure the release is
# not flagged as the latest release, which means that people will not get this
# version of the package unless requested explicitly
- name: publish nightly release
+1 -1
View File
@@ -1,7 +1,7 @@
name: Automatically add new TechDocs Issues and PRs to the GitHub project board
# Development of TechDocs in Backstage is managed by this Kanban board - https://github.com/orgs/backstage/projects/1
# New issues and PRs with TechDocs in their title or docs-like-code label will be added to the board.
# Caveat: New PRs created from forks will not be added since GitHub actions don't share credentials with forks.
# Caveat: New PRs created from forks will not be added since GitHub Actions don't share credentials with forks.
on:
issues:
+22 -1
View File
@@ -124,7 +124,7 @@ service. The `@backstage/create-app` tool that is used to create your own
Backstage app is similar to
[`create-react-app`](https://github.com/facebook/create-react-app) in that it
gives you a starting point. The code you get is meant to be evolved, and most of
the functionality you get out of the box is brought in via NPM dependencies.
the functionality you get out of the box is brought in via npm dependencies.
Keeping your app up to date generally means keeping your dependencies up to
date. The Backstage CLI provides a command to help you with that. Simply run
`yarn backstage-cli versions:bump` at the root of your repo, and the latest
@@ -152,6 +152,27 @@ plugins to share dependencies between each other when possible. This contributes
to Backstage being fast, which is an important part of the user and developer
experience.
### Why are there no published Docker images or helm charts for Backstage?
As mentioned above, Backstage is not a packaged service that you can use out of
the box. In order to get started with Backstage you need to use the
`@backstage/create-app` package to create and customize your own Backstage app.
In order to build a Docker image from your own app, you can use the
`yarn build-image` command which is included out of the box in the app template.
By default this image will bundle up both the frontend and the backend into a
single image that you can deploy using your favorite tooling.
There are also some examples that can help you deploy Backstage to kubernetes in
the
[contrib](https://github.com/backstage/backstage/tree/master/contrib/kubernetes)
folder.
It is possible that example images will be provided in the future, which can be
used to quickly try out a small subset of the functionality of Backstage, but
these would not be able to provide much more functionality on top of what you
can see on a demo site.
### Do I have to write plugins in TypeScript?
No, you can use JavaScript if you prefer. We want to keep the Backstage core
@@ -58,7 +58,7 @@ discover existing functionality in the ecosystem.
APIs are implemented by components and make their boundaries explicit. They
might be defined using an RPC IDL (e.g. in Protobuf, GraphQL or similar), a data
schema (e.g. in Avro, TFRecord or similar), or as code interfaces (e.g.
framework APIs in Swift, Kotlin, Java, C++, Typescript etc). In any case, APIs
framework APIs in Swift, Kotlin, Java, C++, TypeScript etc). In any case, APIs
exposed by components need to be in a known machine-readable format so we can
build further tooling and analysis on top.
@@ -6,7 +6,7 @@ description: Architecture Decision Record (ADR) log on Avoid React.FC and React.
## Context
Facebook has removed `React.FC` from their base template for a Typescript
Facebook has removed `React.FC` from their base template for a TypeScript
project. The reason for this was that it was found to be an unnecessary feature
with next to no benefits in combination with a few downsides.
@@ -407,7 +407,7 @@ The current set of well-known and common values for this field is:
- `service` - a backend service, typically exposing an API
- `website` - a website
- `library` - a software library, such as an NPM module or a Java library
- `library` - a software library, such as an npm module or a Java library
### `spec.lifecycle` [required]
@@ -558,7 +558,7 @@ The current set of well-known and common values for this field is:
- `service` - a backend service, typically exposing an API
- `website` - a website
- `library` - a software library, such as an NPM module or a Java library
- `library` - a software library, such as an npm module or a Java library
### `spec.templater` [required]
+2 -2
View File
@@ -21,7 +21,7 @@ looking at. In response, it receives the static files (HTML, CSS, JSON, etc.) to
render on the page in TechDocs/Backstage.
The static files consist of HTML, CSS and Images generated by MkDocs. We remove
all the Javascript before adding them to Backstage for security reasons. And
all the JavaScript before adding them to Backstage for security reasons. And
there are some additional techdocs metadata JSON files that TechDocs needs to
render a site.
@@ -59,7 +59,7 @@ Similar to how it is done in the Basic setup, the TechDocs Reader requests
your configured storage solution for the necessary files and returns them to
TechDocs Reader.
We will provide instructions, scripts and/or templates (e.g. GitHub actions) to
We will provide instructions, scripts and/or templates (e.g. GitHub Actions) to
generate docs in your CI/CD system.
[Track progress here.](https://github.com/backstage/backstage/issues/3400) You
will be able to use `techdocs-cli` to generate docs and publish the generated docs
@@ -11,7 +11,7 @@ add an existing plugin to it. We are using the
[CircleCI](https://github.com/backstage/backstage/blob/master/plugins/circleci/README.md)
plugin in this example.
1. Add the plugin's NPM package to the repo:
1. Add the plugin's npm package to the repo:
```bash
yarn add @backstage/plugin-circleci
+1 -1
View File
@@ -87,7 +87,7 @@ You may encounter the following error message:
Couldn't find any versions for "file-saver" that matches "eligrey-FileSaver.js-1.3.8.tar.gz-art-external"
```
This is likely because you have a globally configured NPM proxy, which breaks
This is likely because you have a globally configured npm proxy, which breaks
the installation of the `material-table` dependency. This is a known issue and
being worked on in `material-table`, but for now you can work around it using
the following:
+1 -1
View File
@@ -10,7 +10,7 @@ you're planning to do.
Creating a standalone instance makes it simpler to customize the application for
your needs whilst staying up to date with the project. You will also depend on
`@backstage` packages from NPM, making the project much smaller. This is the
`@backstage` packages from npm, making the project much smaller. This is the
recommended approach if you want to kick the tyres of Backstage or setup your
own instance.
+4 -4
View File
@@ -1,16 +1,16 @@
---
id: publishing
title: Publishing
description: Documentation on Publishing NPM packages
description: Documentation on Publishing npm packages
---
## NPM
## npm
NPM packages are published through CI/CD in the
npm packages are published through CI/CD in the
[.github/workflows/master.yml](https://github.com/backstage/backstage/blob/master/.github/workflows/master.yml)
workflow. Every commit that is merged to master will be checked for new versions
of all public packages, and any new versions will automatically be published to
NPM.
npm.
### Creating a new release
+2 -2
View File
@@ -234,7 +234,7 @@ This is especially true for edge cases!
## Non-React Classes
Testing a Javascript object which is _not_ a React component follows a lot of
Testing a JavaScript object which is _not_ a React component follows a lot of
the same principles as testing objects in other languages.
### API Testing Principles
@@ -243,7 +243,7 @@ Testing an API involves verifying four things:
1. Invalid inputs are caught before being sent to the server.
2. Valid inputs translate into a valid browser request.
3. Server response is translated into an expected Javascript object.
3. Server response is translated into an expected JavaScript object.
4. Server errors are handled gracefully.
### Mocking API Calls
+1 -1
View File
@@ -161,7 +161,7 @@ are separated out into their own folder, see further down.
- [`docgen/`](https://github.com/backstage/backstage/tree/master/packages/docgen) -
Uses the
[Typescript Compiler API](https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API)
[TypeScript Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API)
to read out definitions and generate documentation for it.
- [`e2e-test/`](https://github.com/backstage/backstage/tree/master/packages/e2e-test) -
+1 -1
View File
@@ -22,7 +22,7 @@ music and wants to have a theme tune for every service in Backstage.
Sam built a Spotify plugin for Backstage that allows service owners to define a
theme tune for their service. The theme tune plays whenever a user visits the
service page in Backstage. The plugin is published to NPM and available for any
service page in Backstage. The plugin is published to npm and available for any
organization to easily install and add to their Backstage installation.
# 1. A New Plugin
@@ -1,6 +1,6 @@
---
title: Announcing Backstage
author: Stefan Ålund
author: Stefan Ålund, Spotify
authorURL: http://twitter.com/stalund
authorImageURL: https://pbs.twimg.com/profile_images/121166861/6919c047c0d0edaace78c3009b28e917-user-full-200-130.generated_400x400.jpg
---
@@ -1,6 +1,6 @@
---
title: What the heck is Backstage anyway?
author: Stefan Ålund
author: Stefan Ålund, Spotify
authorURL: http://twitter.com/stalund
authorImageURL: https://pbs.twimg.com/profile_images/121166861/6919c047c0d0edaace78c3009b28e917-user-full-200-130.generated_400x400.jpg
---
@@ -1,6 +1,6 @@
---
title: Introducing Lighthouse for Backstage
author: Paul Marbach
author: Paul Marbach, Spotify
authorURL: http://twitter.com/fastfrwrd
authorImageURL: https://pbs.twimg.com/profile_images/1224058798958088192/JPxS8uzR_400x400.jpg
---
@@ -1,6 +1,6 @@
---
title: How to quickly set up Backstage
author: Marcus Eide
author: Marcus Eide, Spotify
authorURL: https://github.com/marcuseide
authorImageURL: https://secure.gravatar.com/avatar/20223f1e03673c7c1e6282fbebaf6942
---
@@ -1,6 +1,6 @@
---
title: Introducing Tech Radar for Backstage
author: Bilawal Hameed
author: Bilawal Hameed, Spotify
authorURL: http://twitter.com/bilawalhameed
authorImageURL: https://avatars0.githubusercontent.com/bih
---
@@ -1,6 +1,6 @@
---
title: Weaveworks COVID-19 app uses Backstage UI
author: Jeff Feng
author: Jeff Feng, Spotify
authorURL: https://github.com/fengypants
authorImageURL: https://avatars2.githubusercontent.com/u/46946747
---
@@ -1,6 +1,6 @@
---
title: Starting Phase 2: The Service Catalog
author: Stefan Ålund
author: Stefan Ålund, Spotify
authorURL: http://twitter.com/stalund
authorImageURL: https://pbs.twimg.com/profile_images/121166861/6919c047c0d0edaace78c3009b28e917-user-full-200-130.generated_400x400.jpg
---
@@ -1,6 +1,6 @@
---
title: Backstage Service Catalog released in alpha
author: Stefan Ålund
author: Stefan Ålund, Spotify
authorURL: http://twitter.com/stalund
image: https://backstage.io/blog/assets/6/header.png
---
@@ -1,6 +1,6 @@
---
title: How to enable authentication in Backstage using Passport
author: Lee Mills
author: Lee Mills, Spotify
authorURL: https://github.com/leemills83
authorImageURL: https://avatars1.githubusercontent.com/u/1236238?s=460&v=4
---
@@ -1,6 +1,6 @@
---
title: Announcing Backstage Software Templates
author: Stefan Ålund
author: Stefan Ålund, Spotify
authorURL: https://twitter.com/stalund
---
@@ -1,6 +1,6 @@
---
title: Announcing TechDocs: Spotifys docs-like-code plugin for Backstage
author: Gary Niemen
author: Gary Niemen, Spotify
authorURL: https://github.com/garyniemen
---
@@ -1,6 +1,6 @@
---
title: Backstage has been accepted into the CNCF Sandbox
author: Stefan Ålund
author: Stefan Ålund, Spotify
authorURL: https://twitter.com/stalund
---
@@ -1,6 +1,6 @@
---
title: How to design for Backstage (even if youre not a designer)
author: Kat Zhou
author: Kat Zhou, Spotify
authorURL: http://twitter.com/katherinemzhou
---
@@ -1,6 +1,6 @@
---
title: The Plugin Marketplace is open
author: Stefan Ålund
author: Stefan Ålund, Spotify
authorURL: https://twitter.com/stalund
---
@@ -1,6 +1,6 @@
---
title: New Cost Insights plugin: The engineers solution to taming cloud costs
author: Janisa Anandamohan
author: Janisa Anandamohan, Spotify
authorURL: https://twitter.com/janisa_a
---
-8
View File
@@ -8,9 +8,6 @@
// See https://docusaurus.io/docs/site-config for all the possible
// site configuration options.
// List of projects/orgs using your project for the users page.
const users = [];
const siteConfig = {
title: 'Backstage Service Catalog and Developer Platform', // Title for your website.
tagline: 'An open platform for building developer portals',
@@ -72,11 +69,6 @@ const siteConfig = {
navGroupSubcategoryTitleColor: '#9e9e9e',
},
/* Colors for syntax highlighting */
highlight: {
theme: 'dark',
},
// This copyright info is used in /core/Footer.js and blog RSS/Atom feeds.
copyright: `Copyright © ${new Date().getFullYear()} Backstage Project Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage`,
+1 -1
View File
@@ -71,7 +71,7 @@ nav:
- Testing:
- Overview: 'plugins/testing.md'
- Publishing:
- Open source and NPM: 'plugins/publishing.md'
- Open source and npm: 'plugins/publishing.md'
- Private/internal (non-open source): 'plugins/publish-private.md'
- Configuration:
- Overview: 'conf/index.md'
@@ -22,7 +22,10 @@ import {
import { EmptyState } from '@backstage/core';
import {
ApiDefinitionCard,
Router as ApiDocsRouter,
ConsumedApisCard,
ConsumingComponentsCard,
ProvidedApisCard,
ProvidingComponentsCard,
} from '@backstage/plugin-api-docs';
import {
AboutCard,
@@ -187,6 +190,17 @@ const ComponentOverviewContent = ({ entity }: { entity: Entity }) => (
</Grid>
);
const ComponentApisContent = ({ entity }: { entity: Entity }) => (
<Grid container spacing={3} alignItems="stretch">
<Grid item md={6}>
<ProvidedApisCard entity={entity} />
</Grid>
<Grid item md={6}>
<ConsumedApisCard entity={entity} />
</Grid>
</Grid>
);
const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout>
<EntityPageLayout.Content
@@ -207,7 +221,7 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout.Content
path="/api/*"
title="API"
element={<ApiDocsRouter entity={entity} />}
element={<ComponentApisContent entity={entity} />}
/>
<EntityPageLayout.Content
path="/docs/*"
@@ -308,6 +322,14 @@ const ApiOverviewContent = ({ entity }: { entity: Entity }) => (
<Grid item md={6}>
<AboutCard entity={entity} />
</Grid>
<Grid container item md={12}>
<Grid item md={6}>
<ProvidingComponentsCard entity={entity} />
</Grid>
<Grid item md={6}>
<ConsumingComponentsCard entity={entity} />
</Grid>
</Grid>
</Grid>
);
+10 -1
View File
@@ -3,11 +3,20 @@
"version": "0.2.5",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"private": true,
"license": "Apache-2.0",
"private": true,
"engines": {
"node": "12 || 14"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/backend"
},
"keywords": [
"backstage"
],
"scripts": {
"build": "backstage-cli backend:build",
"build-image": "backstage-cli backend:build-image --build --tag example-backend",
+9
View File
@@ -11,6 +11,15 @@
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/catalog-client"
},
"keywords": [
"backstage"
],
"scripts": {
"build": "backstage-cli build",
"lint": "backstage-cli lint",
@@ -10,3 +10,5 @@ spec:
- ./apis/spotify-api.yaml
- ./apis/streetlights-api.yaml
- ./apis/swapi-graphql.yaml
- ./apis/wayback-archive-api.yaml
- ./apis/wayback-search-api.yaml
@@ -14,3 +14,5 @@ spec:
- ./components/playback-lib-component.yaml
- ./components/www-artist-component.yaml
- ./components/shuffle-api-component.yaml
- ./components/wayback-archive-component.yaml
- ./components/wayback-search-component.yaml
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: wayback-archive
description: Archive API for the wayback machine
spec:
type: openapi
lifecycle: production
owner: archive@example.com
definition:
$text: https://github.com/APIs-guru/openapi-directory/blob/master/APIs/archive.org/wayback/1.0.0/openapi.yaml
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: wayback-search
description: Search API for the wayback machine
spec:
type: openapi
lifecycle: production
owner: archive@example.com
definition:
$text: https://github.com/APIs-guru/openapi-directory/blob/master/APIs/archive.org/search/1.0.0/openapi.yaml
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: wayback-archive
description: Archive of the wayback machine
spec:
type: service
lifecycle: production
owner: archive@example.com
providesApis:
- wayback-archive
@@ -0,0 +1,13 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: wayback-search
description: Search of the wayback machine
spec:
type: service
lifecycle: production
owner: archive@example.com
providesApis:
- wayback-search
consumesApis:
- wayback-archive
+9
View File
@@ -11,6 +11,15 @@
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/catalog-model"
},
"keywords": [
"backstage"
],
"scripts": {
"build": "backstage-cli build",
"lint": "backstage-cli lint",
+1 -1
View File
@@ -4,7 +4,7 @@ This package provides a CLI for developing Backstage plugins and apps.
## Installation
Install the package via npm or yarn:
Install the package via npm or Yarn:
```sh
$ npm install --save @backstage/cli
+1 -1
View File
@@ -44,7 +44,7 @@
"@sucrase/webpack-loader": "^2.0.0",
"@svgr/plugin-jsx": "5.4.x",
"@svgr/plugin-svgo": "5.4.x",
"@svgr/rollup": "5.4.x",
"@svgr/rollup": "5.5.x",
"@svgr/webpack": "5.4.x",
"@types/start-server-webpack-plugin": "^2.2.0",
"@types/webpack-env": "^1.15.2",
+3 -3
View File
@@ -87,9 +87,9 @@ export function registerCommands(program: CommanderStatic) {
'Create plugin with the backend dependencies as default',
)
.description('Creates a new plugin in the current repository')
.option('--scope <scope>', 'NPM scope')
.option('--npm-registry <URL>', 'NPM registry URL')
.option('--no-private', 'Public NPM Package')
.option('--scope <scope>', 'npm scope')
.option('--npm-registry <URL>', 'npm registry URL')
.option('--no-private', 'Public npm package')
.action(
lazy(() => import('./create-plugin/createPlugin').then(m => m.default)),
);
+1 -1
View File
@@ -90,7 +90,7 @@ type Options = {
* will be suitable for packaging e.g. into a docker image.
*
* This creates a structure that is functionally similar to if the packages where
* installed from NPM, but uses yarn workspaces to link to them at runtime.
* installed from npm, but uses Yarn workspaces to link to them at runtime.
*/
export async function createDistWorkspace(
packageNames: string[],
+1 -1
View File
@@ -37,7 +37,7 @@ type LockfileQueryEntry = {
version: string;
};
/** Entries that have an invalid version range, for example an NPM tag */
/** Entries that have an invalid version range, for example an npm tag */
type AnalyzeResultInvalidRange = {
name: string;
range: string;
+1 -1
View File
@@ -62,7 +62,7 @@
![](https://user-images.githubusercontent.com/872486/93851658-1a76f200-fce3-11ea-990b-26ca1a327a15.png)
- b79017fd3: Updated the `GithubAuth.create` method to configure the default scope of the Github Auth Api. As a result the
- b79017fd3: Updated the `GithubAuth.create` method to configure the default scope of the GitHub Auth Api. As a result the
default scope is configurable when overwriting the Core Api in the app.
```
@@ -45,7 +45,7 @@ export type GithubAuthResponse = {
const DEFAULT_PROVIDER = {
id: 'github',
title: 'Github',
title: 'GitHub',
icon: GithubIcon,
};
@@ -21,7 +21,7 @@ import { OAuthApiCreateOptions } from '../types';
const DEFAULT_PROVIDER = {
id: 'gitlab',
title: 'Gitlab',
title: 'GitLab',
icon: GitlabIcon,
};
+2 -2
View File
@@ -21,7 +21,7 @@
### Patch Changes
- 7b37d65fd: Adds the MarkdownContent component to render and display Markdown content with the default
[GFM](https://github.github.com/gfm/) (Github flavored Markdown) dialect.
[GFM](https://github.github.com/gfm/) (GitHub Flavored Markdown) dialect.
```
<MarkdownContent content={markdownGithubFlavored} />
@@ -59,7 +59,7 @@
- 482b6313d: Fix dense in Structured Metadata Table
- 1c60f716e: Added EmptyState component
- b79017fd3: Updated the `GithubAuth.create` method to configure the default scope of the Github Auth Api. As a result the
- b79017fd3: Updated the `GithubAuth.create` method to configure the default scope of the GitHub Auth Api. As a result the
default scope is configurable when overwriting the Core Api in the app.
```
+1 -1
View File
@@ -4,7 +4,7 @@ This package provides the core API used by Backstage plugins and apps.
## Installation
Install the package via npm or yarn:
Install the package via npm or Yarn:
```sh
$ npm install --save @backstage/core
@@ -20,8 +20,7 @@ import { BackstageTheme } from '@backstage/theme';
import { EmptyState } from './EmptyState';
import { CodeSnippet } from '../CodeSnippet';
const COMPONENT_YAML = `# Example
apiVersion: backstage.io/v1alpha1
const COMPONENT_YAML = `apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: example
@@ -31,8 +30,7 @@ metadata:
spec:
type: website
lifecycle: production
owner: guest
`;
owner: guest`;
type Props = {
annotation: string;
@@ -49,10 +47,10 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
export const MissingAnnotationEmptyState = ({ annotation }: Props) => {
const classes = useStyles();
const description = (
<Typography>
<>
The <code>{annotation}</code> annotation is missing. You need to add the
annotation to your component if you want to enable this tool.
</Typography>
</>
);
return (
<EmptyState
@@ -70,7 +68,7 @@ export const MissingAnnotationEmptyState = ({ annotation }: Props) => {
text={COMPONENT_YAML.replace('ANNOTATION', annotation)}
language="yaml"
showLineNumbers
highlightedNumbers={[7, 8]}
highlightedNumbers={[6, 7]}
customStyle={{ background: 'inherit', fontSize: '115%' }}
/>
</div>
+1 -1
View File
@@ -212,7 +212,7 @@
--config ../../app-config.yaml --config ../../app-config.development.yaml
```
- 5a920c6e4: Updated naming of environment variables. New pattern [NAME]\_TOKEN for Github, Gitlab, Azure & Github enterprise access tokens.
- 5a920c6e4: Updated naming of environment variables. New pattern [NAME]\_TOKEN for GitHub, GitLab, Azure & GitHub Enterprise access tokens.
### Detail:
@@ -13,26 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Router as GitHubActionsRouter,
isPluginApplicableToEntity as isGitHubActionsAvailable,
} from '@backstage/plugin-github-actions';
import {
Router as CircleCIRouter,
isPluginApplicableToEntity as isCircleCIAvailable,
} from '@backstage/plugin-circleci';
import { Router as ApiDocsRouter } from '@backstage/plugin-api-docs';
import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs';
import React from 'react';
import {
EntityPageLayout,
useEntity,
AboutCard,
} from '@backstage/plugin-catalog';
import { Entity } from '@backstage/catalog-model';
import { Grid } from '@material-ui/core';
import { ApiEntity, Entity } from '@backstage/catalog-model';
import { WarningPanel } from '@backstage/core';
import {
ApiDefinitionCard,
ConsumedApisCard,
ConsumingComponentsCard,
ProvidedApisCard,
ProvidingComponentsCard
} from '@backstage/plugin-api-docs';
import {
AboutCard, EntityPageLayout,
useEntity
} from '@backstage/plugin-catalog';
import {
isPluginApplicableToEntity as isCircleCIAvailable, Router as CircleCIRouter
} from '@backstage/plugin-circleci';
import {
isPluginApplicableToEntity as isGitHubActionsAvailable, Router as GitHubActionsRouter
} from '@backstage/plugin-github-actions';
import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs';
import { Grid } from '@material-ui/core';
import React from 'react';
const CICDSwitcher = ({ entity }: { entity: Entity }) => {
// This component is just an example of how you can implement your company's logic in entity page.
@@ -60,6 +63,17 @@ const OverviewContent = ({ entity }: { entity: Entity }) => (
</Grid>
);
const ComponentApisContent = ({ entity }: { entity: Entity }) => (
<Grid container spacing={3} alignItems="stretch">
<Grid item md={6}>
<ProvidedApisCard entity={entity} />
</Grid>
<Grid item md={6}>
<ConsumedApisCard entity={entity} />
</Grid>
</Grid>
);
const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout>
<EntityPageLayout.Content
@@ -75,7 +89,7 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout.Content
path="/api/*"
title="API"
element={<ApiDocsRouter entity={entity} />}
element={<ComponentApisContent entity={entity} />}
/>
<EntityPageLayout.Content
path="/docs/*"
@@ -120,8 +134,7 @@ const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
</EntityPageLayout>
);
export const EntityPage = () => {
const { entity } = useEntity();
export const ComponentEntityPage = ({ entity }: { entity: Entity }) => {
switch (entity?.spec?.type) {
case 'service':
return <ServiceEntityPage entity={entity} />;
@@ -131,3 +144,55 @@ export const EntityPage = () => {
return <DefaultEntityPage entity={entity} />;
}
};
const ApiOverviewContent = ({ entity }: { entity: Entity }) => (
<Grid container spacing={3}>
<Grid item md={6}>
<AboutCard entity={entity} />
</Grid>
<Grid container item md={12}>
<Grid item md={6}>
<ProvidingComponentsCard entity={entity} />
</Grid>
<Grid item md={6}>
<ConsumingComponentsCard entity={entity} />
</Grid>
</Grid>
</Grid>
);
const ApiDefinitionContent = ({ entity }: { entity: ApiEntity }) => (
<Grid container spacing={3}>
<Grid item xs={12}>
<ApiDefinitionCard apiEntity={entity} />
</Grid>
</Grid>
);
const ApiEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout>
<EntityPageLayout.Content
path="/*"
title="Overview"
element={<ApiOverviewContent entity={entity} />}
/>
<EntityPageLayout.Content
path="/definition/*"
title="Definition"
element={<ApiDefinitionContent entity={entity as ApiEntity} />}
/>
</EntityPageLayout>
);
export const EntityPage = () => {
const { entity } = useEntity();
switch (entity?.kind?.toLowerCase()) {
case 'component':
return <ComponentEntityPage entity={entity} />;
case 'api':
return <ApiEntityPage entity={entity} />;
default:
return <DefaultEntityPage entity={entity} />;
}
};
+1 -1
View File
@@ -6,7 +6,7 @@ This package provides utilities that help in developing plugins for Backstage, l
## Installation
Install the package via npm or yarn:
Install the package via npm or Yarn:
```sh
$ npm install --save-dev @backstage/dev-utils
@@ -27,7 +27,7 @@ const COMMIT_SHA =
execSync('git rev-parse HEAD').toString('utf8').trim();
/**
* The GithubMarkdownPrinter is a MarkdownPrinter for printing Github-flavored markdown documents.
* The GithubMarkdownPrinter is a MarkdownPrinter for printing GitHub Flavored Markdown documents.
*/
export default class GithubMarkdownPrinter implements MarkdownPrinter {
private str: string = '';
+1 -1
View File
@@ -31,7 +31,7 @@ export type TypeLink = {
};
/**
* TypeInfo describes a Typescript Type.
* TypeInfo describes a TypeScript Type.
*/
export type TypeInfo = {
id: number;
+9
View File
@@ -11,6 +11,15 @@
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/integration"
},
"keywords": [
"backstage"
],
"scripts": {
"build": "backstage-cli build",
"lint": "backstage-cli lint",
+1 -1
View File
@@ -130,7 +130,7 @@ export async function getProjectId(
const url = new URL(target);
if (!url.pathname.includes('/-/blob/')) {
throw new Error('Please provide full path to yaml file from Gitlab');
throw new Error('Please provide full path to yaml file from GitLab');
}
try {
+1 -1
View File
@@ -4,7 +4,7 @@ This package provides utilities that can be used to test plugins and apps for Ba
## Installation
Install the package via npm or yarn:
Install the package via npm or Yarn:
```sh
$ npm install --save-dev @backstage/test-utils
+1 -1
View File
@@ -4,7 +4,7 @@ This package provides the extended Material UI Theme(s) that power Backstage.
## Installation
Install the package via npm or yarn:
Install the package via npm or Yarn:
```sh
$ npm install --save @backstage/theme
+9
View File
@@ -9,6 +9,15 @@
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/api-docs"
},
"keywords": [
"backstage"
],
"scripts": {
"build": "backstage-cli plugin:build",
"start": "backstage-cli plugin:serve",
@@ -1,51 +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 { ComponentEntity, Entity } from '@backstage/catalog-model';
import { Progress } from '@backstage/core';
import { Grid } from '@material-ui/core';
import React from 'react';
import {
ApiDefinitionCard,
useComponentApiEntities,
useComponentApiNames,
} from '../../components';
type Props = {
entity: Entity;
};
export const EntityPageApi = ({ entity }: Props) => {
const apiNames = useComponentApiNames(entity as ComponentEntity);
const { apiEntities, loading } = useComponentApiEntities({
entity: entity as ComponentEntity,
});
if (loading) {
return <Progress />;
}
return (
<Grid container spacing={3}>
{apiNames.map(api => (
<Grid item xs={12} key={api}>
<ApiDefinitionCard apiEntity={apiEntities!.get(api)} />
</Grid>
))}
</Grid>
);
};
-40
View File
@@ -1,40 +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 { Entity, RELATION_PROVIDES_API } from '@backstage/catalog-model';
import { Route, Routes } from 'react-router';
import { catalogRoute } from '../routes';
import { EntityPageApi } from './EntityPageApi';
import { MissingImplementsApisEmptyState } from './MissingImplementsApisEmptyState';
const isPluginApplicableToEntity = (entity: Entity) => {
// TODO: Also support RELATION_CONSUMES_API
return entity.relations?.some(r => r.type === RELATION_PROVIDES_API);
};
export const Router = ({ entity }: { entity: Entity }) =>
!isPluginApplicableToEntity(entity) ? (
<MissingImplementsApisEmptyState />
) : (
<Routes>
<Route
path={`/${catalogRoute.path}`}
element={<EntityPageApi entity={entity} />}
/>
)
</Routes>
);
@@ -0,0 +1,84 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ApiEntity } from '@backstage/catalog-model';
import { Table, TableColumn } from '@backstage/core';
import React from 'react';
import { ApiTypeTitle } from '../ApiDefinitionCard';
import { EntityLink } from '../EntityLink';
const columns: TableColumn<ApiEntity>[] = [
{
title: 'Name',
field: 'metadata.name',
highlight: true,
render: (entity: any) => (
<EntityLink entity={entity}>{entity.metadata.name}</EntityLink>
),
},
{
title: 'Owner',
field: 'spec.owner',
},
{
title: 'Lifecycle',
field: 'spec.lifecycle',
},
{
title: 'Type',
field: 'spec.type',
render: (entity: ApiEntity) => <ApiTypeTitle apiEntity={entity} />,
},
{
title: 'Description',
field: 'metadata.description',
width: 'auto',
},
];
type Props = {
title: string;
variant?: string;
entities: (ApiEntity | undefined)[];
};
export const ApisTable = ({ entities, title, variant = 'gridItem' }: Props) => {
const tableStyle: React.CSSProperties = {
minWidth: '0',
width: '100%',
};
if (variant === 'gridItem') {
tableStyle.height = 'calc(100% - 10px)';
}
return (
<Table<ApiEntity>
columns={columns}
title={title}
style={tableStyle}
options={{
// TODO: Toolbar padding if off compared to other cards, should be: padding: 16px 24px;
search: false,
paging: false,
actionsColumnIndex: -1,
padding: 'dense',
}}
// TODO: For now we skip all APIs that we can't find without a warning!
data={entities.filter(e => e !== undefined) as ApiEntity[]}
/>
);
};
@@ -0,0 +1,127 @@
/*
* 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, RELATION_CONSUMES_API } from '@backstage/catalog-model';
import { ApiProvider, ApiRegistry } from '@backstage/core';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog';
import { renderInTestApp } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { ApiDocsConfig, apiDocsConfigRef } from '../../config';
import { ConsumedApisCard } from './ConsumedApisCard';
describe('<ConsumedApisCard />', () => {
const apiDocsConfig: jest.Mocked<ApiDocsConfig> = {
getApiDefinitionWidget: jest.fn(),
} as any;
const catalogApi: jest.Mocked<CatalogApi> = {
getLocationById: jest.fn(),
getEntityByName: jest.fn(),
getEntities: jest.fn(),
addLocation: jest.fn(),
getLocationByEntity: jest.fn(),
removeEntityByUid: jest.fn(),
} as any;
let Wrapper: React.ComponentType;
beforeEach(() => {
const apis = ApiRegistry.with(catalogApiRef, catalogApi).with(
apiDocsConfigRef,
apiDocsConfig,
);
Wrapper = ({ children }: { children?: React.ReactNode }) => (
<ApiProvider apis={apis}>{children}</ApiProvider>
);
});
afterEach(() => jest.resetAllMocks());
it('shows empty list if no relations', async () => {
const entity: Entity = {
apiVersion: 'v1',
kind: 'Component',
metadata: {
name: 'my-name',
namespace: 'my-namespace',
},
relations: [],
};
const { getByText } = await renderInTestApp(
<Wrapper>
<ConsumedApisCard entity={entity} />
</Wrapper>,
);
expect(getByText(/Consumed APIs/i)).toBeInTheDocument();
expect(getByText(/No APIs consumed by this entity/i)).toBeInTheDocument();
});
it('shows consumed APIs', async () => {
const entity: Entity = {
apiVersion: 'v1',
kind: 'Component',
metadata: {
name: 'my-name',
namespace: 'my-namespace',
},
relations: [
{
target: {
kind: 'API',
namespace: 'my-namespace',
name: 'target-name',
},
type: RELATION_CONSUMES_API,
},
],
};
catalogApi.getEntityByName.mockResolvedValue({
apiVersion: 'v1',
kind: 'API',
metadata: {
name: 'target-name',
namespace: 'my-namespace',
},
spec: {
type: 'openapi',
owner: 'Test',
lifecycle: 'production',
definition: '...',
},
});
apiDocsConfig.getApiDefinitionWidget.mockReturnValue({
type: 'openapi',
title: 'OpenAPI',
component: () => <div />,
});
const { getByText } = await renderInTestApp(
<Wrapper>
<ConsumedApisCard entity={entity} />
</Wrapper>,
);
await waitFor(() => {
expect(getByText(/Consumed APIs/i)).toBeInTheDocument();
expect(getByText(/target-name/i)).toBeInTheDocument();
expect(getByText(/OpenAPI/)).toBeInTheDocument();
expect(getByText(/Test/i)).toBeInTheDocument();
expect(getByText(/production/i)).toBeInTheDocument();
});
});
});
@@ -0,0 +1,85 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ApiEntity,
Entity,
RELATION_CONSUMES_API,
} from '@backstage/catalog-model';
import { EmptyState, InfoCard, Progress } from '@backstage/core';
import React, { PropsWithChildren } from 'react';
import { ApisTable } from './ApisTable';
import { MissingConsumesApisEmptyState } from '../EmptyState';
import { useRelatedEntities } from '../useRelatedEntities';
const ApisCard = ({
children,
variant = 'gridItem',
}: PropsWithChildren<{ variant?: string }>) => {
return (
<InfoCard variant={variant} title="Consumed APIs">
{children}
</InfoCard>
);
};
type Props = {
entity: Entity;
variant?: string;
};
export const ConsumedApisCard = ({ entity, variant = 'gridItem' }: Props) => {
const { entities, loading, error } = useRelatedEntities(
entity,
RELATION_CONSUMES_API,
);
if (loading) {
return (
<ApisCard variant={variant}>
<Progress />
</ApisCard>
);
}
if (error) {
return (
<ApisCard variant={variant}>
<EmptyState
missing="info"
title="No information to display"
description="There was an error while loading the consumed APIs."
/>
</ApisCard>
);
}
if (!entities || entities.length === 0) {
return (
<ApisCard variant={variant}>
<MissingConsumesApisEmptyState />
</ApisCard>
);
}
return (
<ApisTable
title="Consumed APIs"
variant={variant}
entities={entities as (ApiEntity | undefined)[]}
/>
);
};
@@ -0,0 +1,127 @@
/*
* 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, RELATION_PROVIDES_API } from '@backstage/catalog-model';
import { ApiProvider, ApiRegistry } from '@backstage/core';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog';
import { renderInTestApp } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { ApiDocsConfig, apiDocsConfigRef } from '../../config';
import { ProvidedApisCard } from './ProvidedApisCard';
describe('<ProvidedApisCard />', () => {
const apiDocsConfig: jest.Mocked<ApiDocsConfig> = {
getApiDefinitionWidget: jest.fn(),
} as any;
const catalogApi: jest.Mocked<CatalogApi> = {
getLocationById: jest.fn(),
getEntityByName: jest.fn(),
getEntities: jest.fn(),
addLocation: jest.fn(),
getLocationByEntity: jest.fn(),
removeEntityByUid: jest.fn(),
} as any;
let Wrapper: React.ComponentType;
beforeEach(() => {
const apis = ApiRegistry.with(catalogApiRef, catalogApi).with(
apiDocsConfigRef,
apiDocsConfig,
);
Wrapper = ({ children }: { children?: React.ReactNode }) => (
<ApiProvider apis={apis}>{children}</ApiProvider>
);
});
afterEach(() => jest.resetAllMocks());
it('shows empty list if no relations', async () => {
const entity: Entity = {
apiVersion: 'v1',
kind: 'Component',
metadata: {
name: 'my-name',
namespace: 'my-namespace',
},
relations: [],
};
const { getByText } = await renderInTestApp(
<Wrapper>
<ProvidedApisCard entity={entity} />
</Wrapper>,
);
expect(getByText(/Provided APIs/i)).toBeInTheDocument();
expect(getByText(/No APIs provided by this entity/i)).toBeInTheDocument();
});
it('shows consumed APIs', async () => {
const entity: Entity = {
apiVersion: 'v1',
kind: 'Component',
metadata: {
name: 'my-name',
namespace: 'my-namespace',
},
relations: [
{
target: {
kind: 'API',
namespace: 'my-namespace',
name: 'target-name',
},
type: RELATION_PROVIDES_API,
},
],
};
catalogApi.getEntityByName.mockResolvedValue({
apiVersion: 'v1',
kind: 'API',
metadata: {
name: 'target-name',
namespace: 'my-namespace',
},
spec: {
type: 'openapi',
owner: 'Test',
lifecycle: 'production',
definition: '...',
},
});
apiDocsConfig.getApiDefinitionWidget.mockReturnValue({
type: 'openapi',
title: 'OpenAPI',
component: () => <div />,
});
const { getByText } = await renderInTestApp(
<Wrapper>
<ProvidedApisCard entity={entity} />
</Wrapper>,
);
await waitFor(() => {
expect(getByText(/Provided APIs/i)).toBeInTheDocument();
expect(getByText(/target-name/i)).toBeInTheDocument();
expect(getByText(/OpenAPI/)).toBeInTheDocument();
expect(getByText(/Test/i)).toBeInTheDocument();
expect(getByText(/production/i)).toBeInTheDocument();
});
});
});
@@ -0,0 +1,85 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ApiEntity,
Entity,
RELATION_PROVIDES_API,
} from '@backstage/catalog-model';
import { EmptyState, InfoCard, Progress } from '@backstage/core';
import React, { PropsWithChildren } from 'react';
import { ApisTable } from './ApisTable';
import { MissingProvidesApisEmptyState } from '../EmptyState';
import { useRelatedEntities } from '../useRelatedEntities';
const ApisCard = ({
children,
variant = 'gridItem',
}: PropsWithChildren<{ variant?: string }>) => {
return (
<InfoCard variant={variant} title="Provided APIs">
{children}
</InfoCard>
);
};
type Props = {
entity: Entity;
variant?: string;
};
export const ProvidedApisCard = ({ entity, variant = 'gridItem' }: Props) => {
const { entities, loading, error } = useRelatedEntities(
entity,
RELATION_PROVIDES_API,
);
if (loading) {
return (
<ApisCard variant={variant}>
<Progress />
</ApisCard>
);
}
if (error) {
return (
<ApisCard variant={variant}>
<EmptyState
missing="info"
title="No information to display"
description="There was an error while loading the provided APIs."
/>
</ApisCard>
);
}
if (!entities || entities.length === 0) {
return (
<ApisCard variant={variant}>
<MissingProvidesApisEmptyState />
</ApisCard>
);
}
return (
<ApisTable
title="Provided APIs"
variant={variant}
entities={entities as (ApiEntity | undefined)[]}
/>
);
};
@@ -14,4 +14,5 @@
* limitations under the License.
*/
export { EntityPageApi } from './EntityPageApi';
export { ConsumedApisCard } from './ConsumedApisCard';
export { ProvidedApisCard } from './ProvidedApisCard';
@@ -0,0 +1,87 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ComponentEntity } from '@backstage/catalog-model';
import { Table, TableColumn } from '@backstage/core';
import React from 'react';
import { EntityLink } from '../EntityLink';
const columns: TableColumn<ComponentEntity>[] = [
{
title: 'Name',
field: 'metadata.name',
highlight: true,
render: (entity: any) => (
<EntityLink entity={entity}>{entity.metadata.name}</EntityLink>
),
},
{
title: 'Owner',
field: 'spec.owner',
},
{
title: 'Lifecycle',
field: 'spec.lifecycle',
},
{
title: 'Type',
field: 'spec.type',
},
{
title: 'Description',
field: 'metadata.description',
width: 'auto',
},
];
type Props = {
title: string;
variant?: string;
entities: (ComponentEntity | undefined)[];
};
// TODO: In theory this could also be systems!
export const ComponentsTable = ({
entities,
title,
variant = 'gridItem',
}: Props) => {
const tableStyle: React.CSSProperties = {
minWidth: '0',
width: '100%',
};
if (variant === 'gridItem') {
tableStyle.height = 'calc(100% - 10px)';
}
return (
<Table<ComponentEntity>
columns={columns}
title={title}
style={tableStyle}
options={{
// TODO: Toolbar padding if off compared to other cards, should be: padding: 16px 24px;
search: false,
paging: false,
actionsColumnIndex: -1,
padding: 'dense',
}}
// TODO: For now we skip all APIs that we can't find without a warning!
data={entities.filter(e => e !== undefined) as ComponentEntity[]}
/>
);
};
@@ -0,0 +1,125 @@
/*
* 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, RELATION_API_CONSUMED_BY } from '@backstage/catalog-model';
import { ApiProvider, ApiRegistry } from '@backstage/core';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog';
import { renderInTestApp } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { ConsumingComponentsCard } from './ConsumingComponentsCard';
describe('<ConsumingComponentsCard />', () => {
const catalogApi: jest.Mocked<CatalogApi> = {
getLocationById: jest.fn(),
getEntityByName: jest.fn(),
getEntities: jest.fn(),
addLocation: jest.fn(),
getLocationByEntity: jest.fn(),
removeEntityByUid: jest.fn(),
} as any;
let Wrapper: React.ComponentType;
beforeEach(() => {
const apis = ApiRegistry.with(catalogApiRef, catalogApi);
Wrapper = ({ children }: { children?: React.ReactNode }) => (
<ApiProvider apis={apis}>{children}</ApiProvider>
);
});
afterEach(() => jest.resetAllMocks());
it('shows empty list if no relations', async () => {
const entity: Entity = {
apiVersion: 'v1',
kind: 'API',
metadata: {
name: 'my-name',
namespace: 'my-namespace',
},
spec: {
type: 'openapi',
owner: 'Test',
lifecycle: 'production',
definition: '...',
},
relations: [],
};
const { getByText } = await renderInTestApp(
<Wrapper>
<ConsumingComponentsCard entity={entity} />
</Wrapper>,
);
expect(getByText(/Consumers/i)).toBeInTheDocument();
expect(getByText(/No APIs consumed by this entity/i)).toBeInTheDocument();
});
it('shows consuming components', async () => {
const entity: Entity = {
apiVersion: 'v1',
kind: 'API',
metadata: {
name: 'my-name',
namespace: 'my-namespace',
},
spec: {
type: 'openapi',
owner: 'Test',
lifecycle: 'production',
definition: '...',
},
relations: [
{
target: {
kind: 'Component',
namespace: 'my-namespace',
name: 'target-name',
},
type: RELATION_API_CONSUMED_BY,
},
],
};
catalogApi.getEntityByName.mockResolvedValue({
apiVersion: 'v1',
kind: 'Component',
metadata: {
name: 'target-name',
namespace: 'my-namespace',
},
spec: {
type: 'service',
owner: 'Test',
lifecycle: 'production',
},
});
const { getByText } = await renderInTestApp(
<Wrapper>
<ConsumingComponentsCard entity={entity} />
</Wrapper>,
);
await waitFor(() => {
expect(getByText(/Consumers/i)).toBeInTheDocument();
expect(getByText(/target-name/i)).toBeInTheDocument();
expect(getByText(/Test/i)).toBeInTheDocument();
expect(getByText(/production/i)).toBeInTheDocument();
});
});
});
@@ -0,0 +1,88 @@
/*
* 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 {
ComponentEntity,
Entity,
RELATION_API_CONSUMED_BY,
} from '@backstage/catalog-model';
import { EmptyState, InfoCard, Progress } from '@backstage/core';
import React, { PropsWithChildren } from 'react';
import { MissingConsumesApisEmptyState } from '../EmptyState';
import { useRelatedEntities } from '../useRelatedEntities';
import { ComponentsTable } from './ComponentsTable';
const ComponentsCard = ({
children,
variant = 'gridItem',
}: PropsWithChildren<{ variant?: string }>) => {
return (
<InfoCard variant={variant} title="Consumers">
{children}
</InfoCard>
);
};
type Props = {
entity: Entity;
variant?: string;
};
export const ConsumingComponentsCard = ({
entity,
variant = 'gridItem',
}: Props) => {
const { entities, loading, error } = useRelatedEntities(
entity,
RELATION_API_CONSUMED_BY,
);
if (loading) {
return (
<ComponentsCard variant={variant}>
<Progress />
</ComponentsCard>
);
}
if (error) {
return (
<ComponentsCard variant={variant}>
<EmptyState
missing="info"
title="No information to display"
description="There was an error while loading the consumers."
/>
</ComponentsCard>
);
}
if (!entities || entities.length === 0) {
return (
<ComponentsCard variant={variant}>
<MissingConsumesApisEmptyState />
</ComponentsCard>
);
}
return (
<ComponentsTable
title="Consumers"
variant={variant}
entities={entities as (ComponentEntity | undefined)[]}
/>
);
};
@@ -0,0 +1,125 @@
/*
* 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, RELATION_API_PROVIDED_BY } from '@backstage/catalog-model';
import { ApiProvider, ApiRegistry } from '@backstage/core';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog';
import { renderInTestApp } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { ProvidingComponentsCard } from './ProvidingComponentsCard';
describe('<ProvidingComponentsCard />', () => {
const catalogApi: jest.Mocked<CatalogApi> = {
getLocationById: jest.fn(),
getEntityByName: jest.fn(),
getEntities: jest.fn(),
addLocation: jest.fn(),
getLocationByEntity: jest.fn(),
removeEntityByUid: jest.fn(),
} as any;
let Wrapper: React.ComponentType;
beforeEach(() => {
const apis = ApiRegistry.with(catalogApiRef, catalogApi);
Wrapper = ({ children }: { children?: React.ReactNode }) => (
<ApiProvider apis={apis}>{children}</ApiProvider>
);
});
afterEach(() => jest.resetAllMocks());
it('shows empty list if no relations', async () => {
const entity: Entity = {
apiVersion: 'v1',
kind: 'API',
metadata: {
name: 'my-name',
namespace: 'my-namespace',
},
spec: {
type: 'openapi',
owner: 'Test',
lifecycle: 'production',
definition: '...',
},
relations: [],
};
const { getByText } = await renderInTestApp(
<Wrapper>
<ProvidingComponentsCard entity={entity} />
</Wrapper>,
);
expect(getByText(/Providers/i)).toBeInTheDocument();
expect(getByText(/No APIs provided by this entity/i)).toBeInTheDocument();
});
it('shows providing components', async () => {
const entity: Entity = {
apiVersion: 'v1',
kind: 'API',
metadata: {
name: 'my-name',
namespace: 'my-namespace',
},
spec: {
type: 'openapi',
owner: 'Test',
lifecycle: 'production',
definition: '...',
},
relations: [
{
target: {
kind: 'Component',
namespace: 'my-namespace',
name: 'target-name',
},
type: RELATION_API_PROVIDED_BY,
},
],
};
catalogApi.getEntityByName.mockResolvedValue({
apiVersion: 'v1',
kind: 'Component',
metadata: {
name: 'target-name',
namespace: 'my-namespace',
},
spec: {
type: 'service',
owner: 'Test',
lifecycle: 'production',
},
});
const { getByText } = await renderInTestApp(
<Wrapper>
<ProvidingComponentsCard entity={entity} />
</Wrapper>,
);
await waitFor(() => {
expect(getByText(/Providers/i)).toBeInTheDocument();
expect(getByText(/target-name/i)).toBeInTheDocument();
expect(getByText(/Test/i)).toBeInTheDocument();
expect(getByText(/production/i)).toBeInTheDocument();
});
});
});
@@ -0,0 +1,88 @@
/*
* 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 {
ComponentEntity,
Entity,
RELATION_API_PROVIDED_BY,
} from '@backstage/catalog-model';
import { EmptyState, InfoCard, Progress } from '@backstage/core';
import React, { PropsWithChildren } from 'react';
import { MissingProvidesApisEmptyState } from '../EmptyState';
import { useRelatedEntities } from '../useRelatedEntities';
import { ComponentsTable } from './ComponentsTable';
const ComponentsCard = ({
children,
variant = 'gridItem',
}: PropsWithChildren<{ variant?: string }>) => {
return (
<InfoCard variant={variant} title="Providers">
{children}
</InfoCard>
);
};
type Props = {
entity: Entity;
variant?: string;
};
export const ProvidingComponentsCard = ({
entity,
variant = 'gridItem',
}: Props) => {
const { entities, loading, error } = useRelatedEntities(
entity,
RELATION_API_PROVIDED_BY,
);
if (loading) {
return (
<ComponentsCard variant={variant}>
<Progress />
</ComponentsCard>
);
}
if (error) {
return (
<ComponentsCard variant={variant}>
<EmptyState
missing="info"
title="No information to display"
description="There was an error while loading the providers."
/>
</ComponentsCard>
);
}
if (!entities || entities.length === 0) {
return (
<ComponentsCard variant={variant}>
<MissingProvidesApisEmptyState />
</ComponentsCard>
);
}
return (
<ComponentsTable
title="Providers"
variant={variant}
entities={entities as (ComponentEntity | undefined)[]}
/>
);
};
@@ -14,4 +14,5 @@
* limitations under the License.
*/
export { Router } from './Router';
export { ConsumingComponentsCard } from './ConsumingComponentsCard';
export { ProvidingComponentsCard } from './ProvidingComponentsCard';
@@ -14,16 +14,15 @@
* limitations under the License.
*/
import {
ComponentEntity,
RELATION_PROVIDES_API,
} from '@backstage/catalog-model';
import { renderInTestApp } from '@backstage/test-utils';
import React from 'react';
import { MissingConsumesApisEmptyState } from './MissingConsumesApisEmptyState';
export const useComponentApiNames = (entity: ComponentEntity) => {
// TODO: This code doesn't handle namespaces and kinds correctly, but will be removed soon
return (
entity.relations
?.filter(r => r.type === RELATION_PROVIDES_API)
?.map(r => r.target.name) || []
);
};
describe('<MissingConsumesApisEmptyState />', () => {
it('renders without exploding', async () => {
const { getByText } = await renderInTestApp(
<MissingConsumesApisEmptyState />,
);
expect(getByText(/consumesApis:/i)).toBeInTheDocument();
});
});
@@ -0,0 +1,81 @@
/*
* 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 { Button, makeStyles, Typography } from '@material-ui/core';
import { BackstageTheme } from '@backstage/theme';
import { CodeSnippet, EmptyState } from '@backstage/core';
const COMPONENT_YAML = `# Example
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: example
spec:
type: service
lifecycle: production
owner: guest
consumesApis:
- example-api
`;
const useStyles = makeStyles<BackstageTheme>(theme => ({
code: {
borderRadius: 6,
margin: `${theme.spacing(2)}px 0px`,
background: theme.palette.type === 'dark' ? '#444' : '#fff',
},
}));
export const MissingConsumesApisEmptyState = () => {
const classes = useStyles();
return (
<EmptyState
missing="field"
title="No APIs consumed by this entity"
description={
<>
Components can consume APIs that are displayed on this page. You need
to fill the <code>consumesApis</code> field to enable this tool.
</>
}
action={
<>
<Typography variant="body1">
Link an API to your component as shown in the highlighted example
below:
</Typography>
<div className={classes.code}>
<CodeSnippet
text={COMPONENT_YAML}
language="yaml"
showLineNumbers
highlightedNumbers={[10, 11]}
customStyle={{ background: 'inherit', fontSize: '115%' }}
/>
</div>
<Button
variant="contained"
color="primary"
href="https://backstage.io/docs/features/software-catalog/descriptor-format#specconsumesapis-optional"
>
Read more
</Button>
</>
}
/>
);
};
@@ -0,0 +1,28 @@
/*
* 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 { renderInTestApp } from '@backstage/test-utils';
import React from 'react';
import { MissingProvidesApisEmptyState } from './MissingProvidesApisEmptyState';
describe('<MissingProvidesApisEmptyState />', () => {
it('renders without exploding', async () => {
const { getByText } = await renderInTestApp(
<MissingProvidesApisEmptyState />,
);
expect(getByText(/providesApis:/i)).toBeInTheDocument();
});
});
@@ -40,12 +40,12 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
},
}));
export const MissingImplementsApisEmptyState = () => {
export const MissingProvidesApisEmptyState = () => {
const classes = useStyles();
return (
<EmptyState
missing="field"
title="No APIs implemented by this entity"
title="No APIs provided by this entity"
description={
<>
Components can implement APIs that are displayed on this page. You
@@ -14,4 +14,5 @@
* limitations under the License.
*/
export { MissingImplementsApisEmptyState } from './MissingImplementsApisEmptyState';
export { MissingConsumesApisEmptyState } from './MissingConsumesApisEmptyState';
export { MissingProvidesApisEmptyState } from './MissingProvidesApisEmptyState';
+6 -10
View File
@@ -14,13 +14,9 @@
* limitations under the License.
*/
export type { ApiDefinitionWidget } from './ApiDefinitionCard';
export {
ApiDefinitionCard,
defaultDefinitionWidgets,
} from './ApiDefinitionCard';
export { AsyncApiDefinitionWidget } from './AsyncApiDefinitionWidget';
export { OpenApiDefinitionWidget } from './OpenApiDefinitionWidget';
export { PlainApiDefinitionWidget } from './PlainApiDefinitionWidget';
export { useComponentApiNames } from './useComponentApiNames';
export { useComponentApiEntities } from './useComponentApiEntities';
export * from './ApiDefinitionCard';
export * from './ApisCards';
export * from './AsyncApiDefinitionWidget';
export * from './ComponentsCards';
export * from './OpenApiDefinitionWidget';
export * from './PlainApiDefinitionWidget';
@@ -1,83 +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 { useAsyncRetry } from 'react-use';
import { errorApiRef, useApi } from '@backstage/core';
import {
ApiEntity,
ComponentEntity,
parseEntityName,
} from '@backstage/catalog-model';
import { catalogApiRef } from '@backstage/plugin-catalog';
import { useComponentApiNames } from './useComponentApiNames';
export function useComponentApiEntities({
entity,
}: {
entity: ComponentEntity;
}): {
loading: boolean;
apiEntities?: Map<String, ApiEntity>;
error?: Error;
retry: () => void;
} {
const catalogApi = useApi(catalogApiRef);
const errorApi = useApi(errorApiRef);
const apiNames = useComponentApiNames(entity);
const { loading, value: apiEntities, retry, error } = useAsyncRetry<
Map<string, ApiEntity>
>(async () => {
const resultMap = new Map<string, ApiEntity>();
await Promise.all(
apiNames.map(async name => {
try {
const apiEntityName = parseEntityName(name, {
defaultNamespace: entity.metadata.namespace,
defaultKind: 'API',
});
if (apiEntityName.kind !== 'API') {
throw new Error(
`Referenced entity of kind "${apiEntityName.kind}" as an API`,
);
}
const api = (await catalogApi.getEntityByName(apiEntityName)) as
| ApiEntity
| undefined;
if (api) {
resultMap.set(api.metadata.name, api);
}
} catch (e) {
errorApi.post(e);
}
}),
);
return resultMap;
}, [catalogApi, entity]);
return {
apiEntities,
loading,
error,
retry,
};
}
@@ -0,0 +1,51 @@
/*
* 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 { useApi } from '@backstage/core';
import { catalogApiRef } from '@backstage/plugin-catalog';
import { useAsyncRetry } from 'react-use';
// TODO: Maybe this hook is interesting for others too?
export function useRelatedEntities(
entity: Entity,
type: string,
): {
entities: (Entity | undefined)[] | undefined;
loading: boolean;
error: Error | undefined;
} {
const catalogApi = useApi(catalogApiRef);
const { loading, value, error } = useAsyncRetry<
(Entity | undefined)[]
>(async () => {
const relations =
entity.relations && entity.relations.filter(r => r.type === type);
if (!relations) {
return [];
}
return await Promise.all(
relations?.map(r => catalogApi.getEntityByName(r.target)),
);
}, [entity, type]);
return {
entities: value,
loading,
error,
};
}
-1
View File
@@ -14,6 +14,5 @@
* limitations under the License.
*/
export * from './catalog';
export * from './components';
export { plugin } from './plugin';
-6
View File
@@ -23,9 +23,3 @@ export const rootRoute = createRouteRef({
path: '/api-docs',
title: 'APIs',
});
export const catalogRoute = createRouteRef({
icon: NoIcon,
path: '',
title: 'API',
});
+9
View File
@@ -10,6 +10,15 @@
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/app-backend"
},
"keywords": [
"backstage"
],
"scripts": {
"start": "backstage-cli backend:dev",
"build": "backstage-cli backend:build",
+4 -4
View File
@@ -25,7 +25,7 @@ export AUTH_GOOGLE_CLIENT_ID=x
export AUTH_GOOGLE_CLIENT_SECRET=x
```
### Github
### GitHub
#### Creating a GitHub OAuth application
@@ -42,7 +42,7 @@ export AUTH_GITHUB_CLIENT_ID=x
export AUTH_GITHUB_CLIENT_SECRET=x
```
for github enterprise:
For GitHub Enterprise:
```bash
export AUTH_GITHUB_CLIENT_ID=x
@@ -50,7 +50,7 @@ export AUTH_GITHUB_CLIENT_SECRET=x
export AUTH_GITHUB_ENTERPRISE_INSTANCE_URL=https://x
```
### Gitlab
### GitLab
#### Creating a GitLab OAuth application
@@ -70,7 +70,7 @@ Follow this link, [Add new application](https://gitlab.com/-/profile/application
```bash
export GITLAB_BASE_URL=https://gitlab.com
export AUTH_GITLAB_CLIENT_ID=x # Gitlab calls this the Application ID
export AUTH_GITLAB_CLIENT_ID=x # GitLab calls this the Application ID
export AUTH_GITLAB_CLIENT_SECRET=x
```
+9
View File
@@ -10,6 +10,15 @@
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/auth-backend"
},
"keywords": [
"backstage"
],
"scripts": {
"start": "backstage-cli backend:dev",
"build": "backstage-cli backend:build",
+9
View File
@@ -10,6 +10,15 @@
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/catalog-backend"
},
"keywords": [
"backstage"
],
"scripts": {
"start": "backstage-cli backend:dev",
"build": "backstage-cli backend:build",
+10
View File
@@ -9,6 +9,16 @@
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/catalog-graphql"
},
"keywords": [
"backstage",
"graphql"
],
"scripts": {
"start": "backstage-cli backend:dev",
"build": "backstage-cli backend:build",
+9
View File
@@ -10,6 +10,15 @@
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/catalog-import"
},
"keywords": [
"backstage"
],
"scripts": {
"build": "backstage-cli plugin:build",
"start": "backstage-cli plugin:serve",
+9
View File
@@ -10,6 +10,15 @@
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/catalog"
},
"keywords": [
"backstage"
],
"scripts": {
"build": "backstage-cli plugin:build",
"start": "backstage-cli plugin:serve",
+10
View File
@@ -10,6 +10,16 @@
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/circleci"
},
"keywords": [
"backstage",
"circleci"
],
"scripts": {
"build": "backstage-cli plugin:build",
"lint": "backstage-cli lint",

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