Merge branch 'master' into orkohunter/techdocs-publish-to-cloud-storage
This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Fix React warning of descendant paragraph tag
|
||||
@@ -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.
|
||||
@@ -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} />;
|
||||
+ }
|
||||
+};
|
||||
```
|
||||
@@ -68,9 +68,9 @@ Firekube
|
||||
freben
|
||||
Fredrik
|
||||
github
|
||||
Github
|
||||
GitHub
|
||||
gitlab
|
||||
Gitlab
|
||||
GitLab
|
||||
Grafana
|
||||
graphql
|
||||
graphviz
|
||||
|
||||
@@ -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,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
@@ -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]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) -
|
||||
|
||||
@@ -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
-1
@@ -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: Spotify’s 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 you’re 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 engineer’s solution to taming cloud costs
|
||||
author: Janisa Anandamohan
|
||||
author: Janisa Anandamohan, Spotify
|
||||
authorURL: https://twitter.com/janisa_a
|
||||
---
|
||||
|
||||
|
||||
@@ -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
@@ -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>
|
||||
);
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)),
|
||||
);
|
||||
|
||||
@@ -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[],
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||

|
||||
|
||||
- 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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
```
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
+87
-22
@@ -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} />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 = '';
|
||||
|
||||
@@ -31,7 +31,7 @@ export type TypeLink = {
|
||||
};
|
||||
|
||||
/**
|
||||
* TypeInfo describes a Typescript Type.
|
||||
* TypeInfo describes a TypeScript Type.
|
||||
*/
|
||||
export type TypeInfo = {
|
||||
id: number;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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>
|
||||
);
|
||||
};
|
||||
@@ -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)[]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+2
-1
@@ -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)[]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+2
-1
@@ -14,4 +14,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { Router } from './Router';
|
||||
export { ConsumingComponentsCard } from './ConsumingComponentsCard';
|
||||
export { ProvidingComponentsCard } from './ProvidingComponentsCard';
|
||||
+11
-12
@@ -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();
|
||||
});
|
||||
});
|
||||
+2
-2
@@ -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
|
||||
+2
-1
@@ -14,4 +14,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { MissingImplementsApisEmptyState } from './MissingImplementsApisEmptyState';
|
||||
export { MissingConsumesApisEmptyState } from './MissingConsumesApisEmptyState';
|
||||
export { MissingProvidesApisEmptyState } from './MissingProvidesApisEmptyState';
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -14,6 +14,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './catalog';
|
||||
export * from './components';
|
||||
export { plugin } from './plugin';
|
||||
|
||||
@@ -23,9 +23,3 @@ export const rootRoute = createRouteRef({
|
||||
path: '/api-docs',
|
||||
title: 'APIs',
|
||||
});
|
||||
|
||||
export const catalogRoute = createRouteRef({
|
||||
icon: NoIcon,
|
||||
path: '',
|
||||
title: 'API',
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,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
Reference in New Issue
Block a user