Merge pull request #3 from backstage/master

update master
This commit is contained in:
Peter Grauvogel
2021-03-05 11:17:08 +01:00
committed by GitHub
303 changed files with 5985 additions and 2763 deletions
-7
View File
@@ -1,7 +0,0 @@
---
'@backstage/plugin-kubernetes': patch
---
k8s plugin now surfaces k8s components with only label selector query annotation.
Previously backstage.io/kubernetes-label-selector catalog entity annotation would only work if you also included backstage.io/kubernetes-id.
But backstage.io/kubernetes-id value was ignored
-7
View File
@@ -1,7 +0,0 @@
---
'@backstage/plugin-circleci': patch
'@backstage/plugin-cloudbuild': patch
'@backstage/plugin-github-actions': patch
---
Use Breadcrumbs from @backstage/core rather than material-ui
-6
View File
@@ -1,6 +0,0 @@
---
'@backstage/plugin-scaffolder': patch
'@backstage/plugin-scaffolder-backend': patch
---
Move logic for constructing the template form to the backend, using a new `./parameter-schema` endpoint that returns the form schema for a given template.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/cli': patch
---
Bump `@svgr/webpack` from `5.4.x` to `5.5.x`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-techdocs-backend': patch
---
Forward authorization header on backend request if present
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog-backend': patch
---
GithubDiscoveryProcessor outputs locations as optional to avoid outputting errors for missing locations (see https://github.com/backstage/backstage/issues/4730).
-6
View File
@@ -1,6 +0,0 @@
---
'@backstage/plugin-api-docs': patch
'@backstage/plugin-catalog': patch
---
Update messaging when no entities are in a table.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog': patch
---
Make the external `createComponent` route optional, hiding the "Create Component" button if it isn't bound.
-7
View File
@@ -1,7 +0,0 @@
---
'@backstage/catalog-model': patch
'@backstage/plugin-catalog-backend': patch
'@backstage/plugin-scaffolder-backend': patch
---
Add version `backstage.io/v1beta2` schema for Template entities.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-sonarqube': patch
---
Collect all available metric types if the number exceeds the default page size of 100.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/backend-common': patch
---
Add optional `logClientErrors` to errorHandler to log 4XX errors
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-proxy-backend': patch
---
Added a verification for well formed URLs when processing proxy targets. Otherwise users gets a cryptic error message thrown from Express which makes it hard to debug.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Consider both authentication methods for both `onprem` and `cloud` BitBucket
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-techdocs-backend': patch
---
Use external url for static docs
-10
View File
@@ -1,10 +0,0 @@
---
'@backstage/techdocs-common': patch
'@backstage/plugin-auth-backend': patch
'@backstage/plugin-kafka-backend': patch
'@backstage/plugin-kubernetes-backend': patch
'@backstage/plugin-scaffolder-backend': patch
'@backstage/plugin-techdocs-backend': patch
---
remove usage of res.send() for res.json() and res.end() to ensure content types are more consistently application/json on backend responses and error cases
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/backend-common': patch
---
Change debug log format to print as color grey
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-scaffolder': patch
---
Tweak the template cards to be even more compliant with MUI examples, and a little bit more dense.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-api-docs': patch
---
Add organization name to the API Explorer page title
-23
View File
@@ -1,23 +0,0 @@
---
'@backstage/create-app': patch
---
The api-docs plugin has been migrated to use an [external route reference](https://backstage.io/docs/plugins/composability#binding-external-routes-in-the-app) to dynamically link to the create component page.
If you want to have a button that links to the scaffolder plugin from the API explorer, apply the following changes to `packages/app/src/App.tsx`:
```diff
+ import { apiDocsPlugin } from '@backstage/plugin-api-docs';
import { scaffolderPlugin } from '@backstage/plugin-scaffolder';
const app = createApp({
// ...
bindRoutes({ bind }) {
+ bind(apiDocsPlugin.externalRoutes, {
+ createComponent: scaffolderPlugin.routes.root,
+ });
},
});
```
If you choose to not bind the routes, the button to create new APIs is not displayed.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-kubernetes': patch
---
Support AWS auth provider on kubernetes FE plugin
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-proxy-backend': patch
---
Fix for proxy-backend plugin when global-agent is enabled
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog': patch
---
Use entity relation for the owner of an entity in the catalog entity page header.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/cli': patch
---
Bump `@testing-library/react` in the plugin template from `^10.4.1` to `^11.2.5`. To apply this to an existing plugin, update the dependency in your `package.json`.
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/techdocs-common': patch
'@backstage/plugin-techdocs-backend': patch
'@backstage/plugin-techdocs': patch
---
OpenStack Swift publisher added for tech-docs.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-techdocs': minor
---
Add authorization header on techdocs api requests. Breaking change as clients now needs the Identity API.
-37
View File
@@ -1,37 +0,0 @@
---
'@backstage/plugin-scaffolder': minor
'@backstage/plugin-scaffolder-backend': minor
---
The scaffolder has been updated to support the new `v1beta2` template schema which allows for custom template actions!
See documentation for more information how to create and register new template actions.
**Breaking changes**
The backend scaffolder plugin now needs a `UrlReader` which can be pulled from the PluginEnvironment.
The following change is required in `backend/src/plugins/scaffolder.ts`
```diff
export default async function createPlugin({
logger,
config,
database,
+ reader,
}: PluginEnvironment): Promise<Router> {
// omitted code
return await createRouter({
preparers,
templaters,
publishers,
logger,
config,
dockerClient,
database,
catalogClient,
+ reader,
});
```
-6
View File
@@ -1,6 +0,0 @@
---
'@backstage/core-api': patch
'@backstage/core': patch
---
Added support for optional external route references. By setting `optional: true` when creating an `ExternalRouteRef` it is no longer a requirement to bind the route in the app. If the app isn't bound `useRouteRef` will return `undefined`.
-23
View File
@@ -1,23 +0,0 @@
---
'@backstage/plugin-api-docs': patch
---
The api-docs plugin has been migrated to use an [external route reference](https://backstage.io/docs/plugins/composability#binding-external-routes-in-the-app) to dynamically link to the create component page. This means you need to migrate the api docs plugin to use the new extension components, as well as bind the external route.
To bind the external route from the api docs plugin to the scaffolder template index page, make sure you have the appropriate imports and add the following to the `createApp` call:
```ts
import { apiDocsPlugin } from '@backstage/plugin-api-docs';
import { scaffolderPlugin } from '@backstage/plugin-scaffolder';
const app = createApp({
// ...
bindRoutes({ bind }) {
bind(apiDocsPlugin.externalRoutes, {
createComponent: scaffolderPlugin.routes.root,
});
},
});
```
If you choose to not bind the routes, the button to create new APIs is not displayed.
-6
View File
@@ -1,6 +0,0 @@
---
'@backstage/core-api': patch
'@backstage/core': patch
---
Internal refactor of how component data is access to avoid polluting components and make it possible to bridge across versions.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-splunk-on-call': minor
---
Updated splunk-on-call plugin to use the REST endpoint (incident creation-acknowledgement-resolution).
It implies switching from `splunkOnCall.username` configuration to `splunkOnCall.eventsRestEndpoint` configuration, this is a breaking change.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-sonarqube': patch
---
Display '-' instead of 'undefined' if no code coverage is reported.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/cli': patch
---
New config command to export the configuration schema. When running backstage-cli with yarn, consider using `yarn --silent backstage-cli config:schema` to get a clean output on `stdout`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/core': patch
---
Added the color prop to TrendLine from the Sparklines props types to be able to have custom colors.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/core': patch
---
Added a new useSupportConfig hook that reads a new `app.support` config key. Also updated the SupportButton and ErrorPage components to use the new config.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Fixed file path resolution for templates with a file location
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/core': patch
'@backstage/theme': patch
---
Deprecated `ItemCard`. Added `ItemCardGrid` and `ItemCardHeader` instead, that can be used to compose functionality around regular Material-UI `Card` components instead.
-31
View File
@@ -1,31 +0,0 @@
---
'@backstage/core': minor
'@backstage/plugin-pagerduty': patch
---
- Adds onClick and other props to IconLinkVertical;
- Allows TriggerButton component to render when pager duty key is missing;
- Refactors TriggerButton and PagerDutyCard not to have shared state;
- Removes the `action` prop of the IconLinkVertical component while adding `onClick`.
Instead of having an action including a button with onClick, now the whole component can be clickable making it easier to implement and having a better UX.
Before:
```ts
const myLink: IconLinkVerticalProps = {
label: 'Click me',
action: <Button onClick={myAction} />,
icon: <MyIcon onClick={myAction} />,
};
```
After:
```ts
const myLink: IconLinkVerticalProps = {
label: 'Click me',
onClick: myAction,
icon: <MyIcon />,
};
```
-22
View File
@@ -1,22 +0,0 @@
---
'@backstage/plugin-scaffolder-backend': minor
---
Removed support for deprecated publisher auth configuration within the `scaffolder` configuration block, such as `scaffolder.github.token`. Access should instead be configured through `integrations` configuration.
For example, replace the following configuration in `app-config.yaml`
```yaml
scaffolder:
github:
token: my-token
```
with
```yaml
integrations:
github:
- host: github.com
token: my-token
```
-56
View File
@@ -1,56 +0,0 @@
---
'@backstage/create-app': patch
---
Migrated away from using deprecated routes and router components at top-level in the app, and instead use routable extension pages.
To apply this change to an existing app, make the following changes to `packages/app/src/App.tsx`:
Update imports and remove the usage of the deprecated `app.getRoutes()`.
```diff
-import { Router as DocsRouter } from '@backstage/plugin-techdocs';
+import { TechdocsPage } from '@backstage/plugin-techdocs';
import { CatalogImportPage } from '@backstage/plugin-catalog-import';
-import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar';
-import { SearchPage as SearchRouter } from '@backstage/plugin-search';
-import { Router as SettingsRouter } from '@backstage/plugin-user-settings';
+import { TechRadarPage } from '@backstage/plugin-tech-radar';
+import { SearchPage } from '@backstage/plugin-search';
+import { UserSettingsPage } from '@backstage/plugin-user-settings';
+import { ApiExplorerPage } from '@backstage/plugin-api-docs';
import { EntityPage } from './components/catalog/EntityPage';
import { scaffolderPlugin, ScaffolderPage } from '@backstage/plugin-scaffolder';
const AppProvider = app.getProvider();
const AppRouter = app.getRouter();
-const deprecatedAppRoutes = app.getRoutes();
```
As well as update or add the following routes:
```diff
<Route path="/create" element={<ScaffolderPage />} />
- <Route path="/docs" element={<DocsRouter />} />
+ <Route path="/docs" element={<TechdocsPage />} />
+ <Route path="/api-docs" element={<ApiExplorerPage />} />
<Route
path="/tech-radar"
- element={<TechRadarRouter width={1500} height={800} />}
+ element={<TechRadarPage width={1500} height={800} />}
/>
<Route path="/catalog-import" element={<CatalogImportPage />} />
- <Route
- path="/search"
- element={<SearchRouter/>}
- />
- <Route path="/settings" element={<SettingsRouter />} />
- {deprecatedAppRoutes}
+ <Route path="/search" element={<SearchPage />} />
+ <Route path="/settings" element={<UserSettingsPage />} />
```
If you have added additional plugins with registered routes or are using `Router` components from other plugins, these should be migrated to use the `*Page` components as well. See [this commit](https://github.com/backstage/backstage/commit/abd655e42d4ed416b70848ffdb1c4b99d189f13b) for more examples of how to migrate.
For more information and the background to this change, see the [composability system migration docs](https://backstage.io/docs/plugins/composability).
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-cloudbuild': patch
---
Change terminology for cloudbuild
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/plugin-explore': patch
'@backstage/plugin-scaffolder': patch
'@backstage/plugin-techdocs': patch
---
Make use of the new core `ItemCardGrid` and `ItemCardHeader` instead of the deprecated `ItemCard`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-auth-backend': patch
---
Expose a configuration option for the oidc scope
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog': patch
---
Removed fullScreen property from UnregisterEntity Dialog modal.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-github-actions': patch
---
Use Breadcrumbs from @backstage/core
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-techdocs-backend': patch
---
Enhanced the example documented-component to better demonstrate TechDocs features
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/techdocs-common': patch
---
- Improve deprecation warning messaging in logs.
- Replace temp folder path from git provider domain(`source`) to full git host name (`resource`). (See: https://github.com/IonicaBizau/git-url-parse#giturlparseurl)
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/techdocs-common': patch
'@backstage/plugin-techdocs-backend': patch
---
Refactor log messaging to improve clarity
-7
View File
@@ -1,7 +0,0 @@
---
'@backstage/plugin-jenkins': patch
---
- Add Jenkins logo to project page.
- Move to new Backstage breadcrumb component.
- Change references of deprecated "Job" terminology to "Project" per [Jenkins Glossary](https://www.jenkins.io/doc/book/glossary/).
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes': patch
---
Adds a new method `getClusters` to grab cluster configuration in the frontend
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog-react': patch
---
Fixed EntityProvider setting 'loading' bool erroneously to true
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-auth-backend': patch
---
Change the JWKS url value for the oidc configuration endpoint
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/techdocs-common': patch
---
Remove read-store-upload loop when uploading S3 objects for TechDocs
+1 -1
View File
@@ -94,7 +94,7 @@ techdocs:
generators:
techdocs: 'docker' # Alternatives - 'local'
publisher:
type: 'local' # Alternatives - 'googleGcs' or 'awsS3' or 'azureBlobStorage'. Read documentation for using alternatives.
type: 'local' # Alternatives - 'googleGcs' or 'awsS3' or 'azureBlobStorage' or 'openStackSwift'. Read documentation for using alternatives.
sentry:
organization: my-company
Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 205 KiB

+33 -5
View File
@@ -4,8 +4,38 @@ title: Contributing to Storybook
description: Documentation on How to Contribute to Storybook
---
You find our storybook at
[http://backstage.io/storybook](http://backstage.io/storybook)
The Backstage Storybook provides you a way to explore reusable Backstage User
Interface elements and how to use them in developing the Backstage core and its
plugins. These UI elements are commonly called "components", and include things
such as buttons, tables, specialized widgets with specific formatting, and so
on.
You'll find our storybook at
[http://backstage.io/storybook](http://backstage.io/storybook).
As noted in the [design introduction](design.md), Backstage's design is based
off of [Material-UI](https://material-ui.com/). Much of the UI elements use
direct Material-UI, while we've also extended and written custom ones to provide
specific functionality.
![Storybook Page](../assets/dls/storybook-page.png)
Storybook "Stories" are used to provide examples for how to use a specific
component, which are then displayed both visually and with sample code to be
copied.
When custom Backstage components are created, they are placed in the
`@backstage/core` package and added to the Storybook.
There may be times where an existing Material-UI component (in
`@material-ui/core`) is sufficient and doesn't need to be wrapped or duplicated.
However, we may want to identify an _opinionated_ way to use that component
inside of Backstage. In these cases, stories showing how to use those existing
components will also be put into our storybook.
When a story example using Material-UI becomes more complex, requiring a
specific set of colors, variants, parameters, etc., it may become a candidate to
be refactored to become a full Backstage core component.
## Creating a new Story
@@ -33,12 +63,10 @@ core
Go to `packages/storybook`, run `yarn install` and install the dependencies,
then run the following on your command line: `yarn start`
![](../assets/dls/running-storybook.png)
![Storybook command startup](../assets/dls/running-storybook.png)
_You should see a log like the image above._
If everything worked out, your server will be running on **port 6006**, go to
your browser and navigate to `http://localhost:6006/`. You should be able to
navigate and see the Storybook page.
![](../assets/dls/storybook-page.png)
+4
View File
@@ -127,6 +127,10 @@ your own plugin for Backstage.
**[Discord](https://discord.gg/EBHEGzX)** - all design questions should be
directed to the _#design_ channel.
**[Customize Backstage's look and feel](https://backstage.io/docs/getting-started/app-custom-theme)** -
How to customize the look and feel of your Backstage instance by extending the
theme.
## 🔮 Future
### Contributions from designers
+9 -2
View File
@@ -73,10 +73,17 @@ cluster. Valid values are:
| `serviceAccount` | This will use a Kubernetes [service account](https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/) to access the Kubernetes API. When this is used the `serviceAccountToken` field should also be set. |
| `google` | This will use a user's Google auth token from the [Google auth plugin](https://backstage.io/docs/auth/) to access the Kubernetes API. |
### `clusters.\*.serviceAccount` (optional)
### `clusters.\*.serviceAccountToken` (optional)
The service account token to be used when using the `serviceAccount` auth
provider.
provider. You could get the service account token with:
```sh
kubectl -n <NAMESPACE> get secret $(kubectl -n <NAMESPACE> get sa <SERVICE_ACCOUNT_NAME> -o=json \
| jq -r '.secrets[0].name') -o=json \
| jq -r '.data["token"]' \
| base64 --decode
```
### Role Based Access Control
+5
View File
@@ -117,3 +117,8 @@ Start the frontend and the backend app by
After installing the plugins in the code, you'll need to then
[configure them](configuration.md).
## Troubleshooting
After installing the plugins in the code, if the Kubernetes information is not
showing up, you'll need to [troubleshoot it](troubleshooting.md).
@@ -0,0 +1,99 @@
---
id: troubleshooting
title: Troubleshooting Kubernetes
sidebar_label: Troubleshooting
description: Troubleshooting for Kubernetes
---
## Kubernetes is not showing up on Service Entities
This can be debugged by checking whether your Kubernetes cluster are connected
to Backstage as follows:
```curl
curl --location --request POST '{{backstage-backend-url}}:{{backstage-backend-port}}/api/kubernetes/services/:service-entity-name' \
--header 'Content-Type: application/json' \
--data-raw '{
"entity": {
"metadata": {
"name": <service-entity-name>
}
}
}
'
```
The curl response should have resources from Kubernetes:
```json
# curl response
{
"items": [
{
"cluster": {
"name": <cluster-name>
},
"resources": [
{
"type": "services",
"resources": [
{
"metadata": {
"creationTimestamp": "2022-03-13T13:52:46.000Z",
"labels": {
"app": <k8s-app-name>,
"backstage": <selector>,
"backstage.io/kubernetes-id": <service-entity-name>
},
"name": <k8s-app-name>,
"namespace": <namespace>
},
....
}
]
},
....
{
"type": "pods",
"resources": [
,,,,
]
}
],
"errors": []
}
]
}
```
The Kubernetes tab will not show anything when the catalog info annotation does
not match the related Kubernetes resource. We recommend you add the following
labels to your resources and use the label selector annotation as follows:
`backstage.io/kubernetes-id: <entity-service-name>`for get k8s service-related
objects.
[See the plugin code](https://github.com/backstage/backstage/blob/a1f587c/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts#L119)
```yaml
# k8s related yaml (service.yaml, deployment.yaml, ingress.yaml)
metadata:
creationTimestamp: '2022-03-13T13:52:46.000Z'
labels:
app: <k8s-app-name>
env: <environment>
backstage.io/kubernetes-id: <service-entity-name>
name: <k8s-app-name>
namespace: <namespace>
```
k8s-app-name and service-entity-name could be different, but if you would like
to have consistent names between k8s and backstage, we recommend use same name.
and the catalog info annotations would use label selector:
```yaml
# catalog-info.yaml (backstage)
annotations:
backstage.io/kubernetes-label-selector: '<label-selector>'
```
+2 -3
View File
@@ -1,9 +1,8 @@
---
id: template-legacy
title: Writing Templates (Legacy)
description:
Old documentation describing the backstage.io/v1alpha1 format of the Template
Schema
# prettier-ignore
description: Old documentation describing the backstage.io/v1alpha1 format of the Template Schema
---
## Kind: Template
+1
View File
@@ -54,6 +54,7 @@ providers are used.
| Google Cloud Storage (GCS) | Yes ✅ |
| Amazon Web Services (AWS) S3 | Yes ✅ |
| Azure Blob Storage | Yes ✅ |
| OpenStack Swift | Yes ✅ |
[Reach out to us](#feedback) if you want to request more platforms.
+76 -1
View File
@@ -257,7 +257,8 @@ techdocs:
**3a. (Recommended) Authentication using environment variable**
Set the config `techdocs.publisher.azureBlobStorage.credentials.accountName` in
If you do not prefer (3a) and optionally like to use a service account, you can
set the config `techdocs.publisher.azureBlobStorage.credentials.accountName` in
your `app-config.yaml` to the your account name.
The storage blob client will automatically use the environment variable
@@ -309,3 +310,77 @@ and read the static generated documentation files. When you start the backend of
the app, you should be able to see
`techdocs info Successfully connected to the Azure Blob Storage container` in
the logs.
## Configuring OpenStack Swift Container with TechDocs
Follow the
[official OpenStack Api documentation](https://docs.openstack.org/api-ref/identity/v3/)
for the latest instructions on the following steps involving OpenStack Storage.
**1. Set `techdocs.publisher.type` config in your `app-config.yaml`**
Set `techdocs.publisher.type` to `'openStackSwift'`.
```yaml
techdocs:
publisher:
type: 'openStackSwift'
```
**2. Create an OpenStack Swift Storage Container**
Create a dedicated container for TechDocs sites.
[Refer to the official documentation](https://docs.openstack.org/mitaka/user-guide/dashboard_manage_containers.html).
TechDocs will publish documentation to this container and will fetch files from
here to serve documentation in Backstage. Note that the container names are
globally unique.
Set the config `techdocs.publisher.openStackSwift.containerName` in your
`app-config.yaml` to the name of the container you just created.
```yaml
techdocs:
publisher:
type: 'openStackSwift'
openStackSwift:
containerName: 'name-of-techdocs-storage-container'
```
**3. Authentication using app-config.yaml**
Set the configs in your `app-config.yaml` to point to your container name.
https://docs.openstack.org/api-ref/identity/v3/?expanded=password-authentication-with-unscoped-authorization-detail#password-authentication-with-unscoped-authorization
for more details.
```yaml
techdocs:
publisher:
type: 'openStackSwift'
openStackSwift:
containerName: 'name-of-techdocs-storage-bucket'
credentials:
userName:
$env: OPENSTACK_SWIFT_STORAGE_USERNAME
password:
$env: OPENSTACK_SWIFT_STORAGE_PASSWORD
authUrl:
$env: OPENSTACK_SWIFT_STORAGE_AUTH_URL
keystoneAuthVersion:
$env: OPENSTACK_SWIFT_STORAGE_AUTH_VERSION
domainId:
$env: OPENSTACK_SWIFT_STORAGE_DOMAIN_ID
domainName:
$env: OPENSTACK_SWIFT_STORAGE_DOMAIN_NAME
region:
$env: OPENSTACK_SWIFT_STORAGE_REGION
```
**4. That's it!**
Your Backstage app is now ready to use OpenStack Swift Storage for TechDocs, to
store and read the static generated documentation files. When you start the
backend of the app, you should be able to see
`techdocs info Successfully connected to the OpenStack Swift Storage container`
in the logs.
@@ -4,20 +4,30 @@ title: Configuring App with plugins
description: Documentation on How Configuring App with plugins
---
Backstage plugins customize the app for your needs. There is a
[plugin marketplace](https://backstage.io/plugins) with plugins for many common
infrastructure needs - CI/CD, monitoring, auditing, and more.
## Adding existing plugins to your app
The following steps assume that you have created a new Backstage app and want to
add an existing plugin to it. We are using the
The following steps assume that you have
[created a Backstage app](./create-an-app.md) and want to 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:
```bash
yarn add @backstage/plugin-circleci
yarn workspace app add @backstage/plugin-circleci
```
2. Add the plugin itself:
Note the plugin is added to the `app` package, rather than the root
package.json. Backstage Apps are set up as monorepos with
[yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/). Since
CircleCI is a frontend UI plugin, it goes in `app` rather than `backend`.
2. Add the plugin itself to the App:
```js
// packages/app/src/plugins.ts
+15 -27
View File
@@ -27,9 +27,9 @@ The required steps in the host build are to install dependencies with
`yarn install`, generate type definitions using `yarn tsc`, and build all
packages with `yarn build`.
> NOTE: Using `yarn build` to build packages and bundle the backend assumes that
> you have migrated to using `backstage-cli backend:bundle` as your build script
> in the backend package.
> NOTE: If you created your app prior to 2021-02-18, follow the
> [migration step](https://github.com/backstage/backstage/releases/tag/release-2021-02-18)
> to move from `backend:build` to `backend:bundle`.
In a CI workflow it might look something like this:
@@ -43,22 +43,10 @@ yarn tsc
yarn build
```
Once the host build is complete, we are ready to build our image. We use the
following `Dockerfile`, which is also included when creating a new app with
`@backstage/create-app`:
Once the host build is complete, we are ready to build our image. The following
`Dockerfile` is included when creating a new app with `@backstage/create-app`:
```Dockerfile
# This dockerfile builds an image for the backend package.
# It should be executed with the root of the repo as docker context.
#
# Before building this image, be sure to have run the following commands in the repo root:
#
# yarn install
# yarn tsc
# yarn build
#
# Once the commands have been run, you can build the image using `yarn build-image`
FROM node:14-buster-slim
WORKDIR /app
@@ -78,15 +66,15 @@ CMD ["node", "packages/backend", "--config", "app-config.yaml"]
For more details on how the `backend:bundle` command and the `skeleton.tar.gz`
file works, see the
[`backend:bundle` command docs](../cli/commands.md#backendbundle)
[`backend:bundle` command docs](../cli/commands.md#backendbundle).
The `Dockerfile` is typically placed at `packages/backend/Dockerfile`, but needs
to be executed with the root of the repo as the build context, in order to get
access to the root `yarn.lock` and `package.json`, along with any other files
that might be needed, such as `.npmrc`.
The `Dockerfile` is located at `packages/backend/Dockerfile`, but needs to be
executed with the root of the repo as the build context, in order to get access
to the root `yarn.lock` and `package.json`, along with any other files that
might be needed, such as `.npmrc`.
In order to speed up the build we can significantly reduce the build context
size using the following `.dockerignore` in the root of the repo:
The `@backstage/create-app` command adds the following `.dockerignore` in the
root of the repo to speed up the build by reducing build context size:
```text
.git
@@ -96,9 +84,9 @@ packages
plugins
```
With the project build and the `.dockerignore` and `Dockerfile` in place, we are
now ready to build the final image. Assuming we're at the root of the repo, we
execute the build like this:
With the project built and the `.dockerignore` and `Dockerfile` in place, we are
now ready to build the final image. From the root of the repo, execute the
build:
```bash
docker image build . -f packages/backend/Dockerfile --tag backstage
+10 -9
View File
@@ -36,15 +36,6 @@
],
"CLI": ["cli/index", "cli/commands"],
"Core Features": [
{
"type": "subcategory",
"label": "Kubernetes",
"ids": [
"features/kubernetes/overview",
"features/kubernetes/installation",
"features/kubernetes/configuration"
]
},
{
"type": "subcategory",
"label": "Software Catalog",
@@ -62,6 +53,16 @@
"features/software-catalog/software-catalog-api"
]
},
{
"type": "subcategory",
"label": "Kubernetes",
"ids": [
"features/kubernetes/overview",
"features/kubernetes/installation",
"features/kubernetes/configuration",
"features/kubernetes/troubleshooting"
]
},
{
"type": "subcategory",
"label": "Software Templates",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

+69
View File
@@ -1,5 +1,74 @@
# example-app
## 0.2.18
### Patch Changes
- Updated dependencies [ba21797ca]
- Updated dependencies [b2a5320a4]
- Updated dependencies [12d8f27a6]
- Updated dependencies [507513fed]
- Updated dependencies [32a003973]
- Updated dependencies [40c0fdbaa]
- Updated dependencies [12d8f27a6]
- Updated dependencies [cfc83cac1]
- Updated dependencies [bc327dc42]
- Updated dependencies [2386de1d3]
- Updated dependencies [42c8ebb79]
- Updated dependencies [10362e9eb]
- Updated dependencies [e37d2de99]
- Updated dependencies [813c6a4f2]
- Updated dependencies [11c6208fe]
- Updated dependencies [8106c9528]
- Updated dependencies [05183f202]
- Updated dependencies [40c0fdbaa]
- Updated dependencies [f71589800]
- Updated dependencies [2a271d89e]
- Updated dependencies [bece09057]
- Updated dependencies [d4f0a1406]
- Updated dependencies [169f48deb]
- Updated dependencies [8a1566719]
- Updated dependencies [9d455f69a]
- Updated dependencies [4c049a1a1]
- Updated dependencies [d21058c34]
- Updated dependencies [b33e553b2]
- Updated dependencies [04667f571]
- Updated dependencies [b93538acc]
- Updated dependencies [0452ba238]
- Updated dependencies [8871e7523]
- Updated dependencies [02816ecd7]
- Updated dependencies [dbea11072]
- @backstage/plugin-kubernetes@0.3.12
- @backstage/plugin-circleci@0.2.11
- @backstage/plugin-cloudbuild@0.2.12
- @backstage/plugin-github-actions@0.3.5
- @backstage/plugin-scaffolder@0.7.0
- @backstage/cli@0.6.3
- @backstage/plugin-api-docs@0.4.8
- @backstage/plugin-catalog@0.4.1
- @backstage/catalog-model@0.7.3
- @backstage/plugin-explore@0.3.0
- @backstage/plugin-techdocs@0.6.0
- @backstage/plugin-catalog-import@0.4.3
- @backstage/core@0.7.0
- @backstage/plugin-catalog-react@0.1.1
- @backstage/plugin-pagerduty@0.3.2
- @backstage/plugin-jenkins@0.3.12
- @backstage/plugin-lighthouse@0.2.13
- @backstage/plugin-cost-insights@0.8.3
- @backstage/plugin-gcp-projects@0.2.5
- @backstage/plugin-gitops-profiles@0.2.6
- @backstage/plugin-graphiql@0.2.8
- @backstage/plugin-kafka@0.2.5
- @backstage/plugin-newrelic@0.2.6
- @backstage/plugin-org@0.3.9
- @backstage/plugin-register-component@0.2.12
- @backstage/plugin-rollbar@0.3.3
- @backstage/plugin-search@0.3.3
- @backstage/plugin-sentry@0.3.8
- @backstage/plugin-tech-radar@0.3.7
- @backstage/plugin-user-settings@0.2.7
## 0.2.17
### Patch Changes
+32 -32
View File
@@ -1,39 +1,39 @@
{
"name": "example-app",
"version": "0.2.17",
"version": "0.2.18",
"private": true,
"bundled": true,
"dependencies": {
"@backstage/catalog-model": "^0.7.2",
"@backstage/cli": "^0.6.2",
"@backstage/core": "^0.6.3",
"@backstage/plugin-api-docs": "^0.4.7",
"@backstage/plugin-catalog": "^0.4.0",
"@backstage/plugin-catalog-import": "^0.4.2",
"@backstage/plugin-catalog-react": "^0.1.0",
"@backstage/plugin-circleci": "^0.2.10",
"@backstage/plugin-cloudbuild": "^0.2.11",
"@backstage/plugin-cost-insights": "^0.8.2",
"@backstage/plugin-explore": "^0.2.7",
"@backstage/plugin-gcp-projects": "^0.2.4",
"@backstage/plugin-github-actions": "^0.3.4",
"@backstage/plugin-gitops-profiles": "^0.2.5",
"@backstage/plugin-graphiql": "^0.2.7",
"@backstage/plugin-jenkins": "^0.3.11",
"@backstage/plugin-kafka": "^0.2.4",
"@backstage/plugin-kubernetes": "^0.3.11",
"@backstage/plugin-lighthouse": "^0.2.12",
"@backstage/plugin-newrelic": "^0.2.5",
"@backstage/plugin-org": "^0.3.8",
"@backstage/plugin-pagerduty": "0.3.1",
"@backstage/plugin-register-component": "^0.2.11",
"@backstage/plugin-rollbar": "^0.3.2",
"@backstage/plugin-scaffolder": "^0.6.0",
"@backstage/plugin-sentry": "^0.3.7",
"@backstage/plugin-search": "^0.3.2",
"@backstage/plugin-tech-radar": "^0.3.6",
"@backstage/plugin-techdocs": "^0.5.8",
"@backstage/plugin-user-settings": "^0.2.6",
"@backstage/catalog-model": "^0.7.3",
"@backstage/cli": "^0.6.3",
"@backstage/core": "^0.7.0",
"@backstage/plugin-api-docs": "^0.4.8",
"@backstage/plugin-catalog": "^0.4.1",
"@backstage/plugin-catalog-import": "^0.4.3",
"@backstage/plugin-catalog-react": "^0.1.1",
"@backstage/plugin-circleci": "^0.2.11",
"@backstage/plugin-cloudbuild": "^0.2.12",
"@backstage/plugin-cost-insights": "^0.8.3",
"@backstage/plugin-explore": "^0.3.0",
"@backstage/plugin-gcp-projects": "^0.2.5",
"@backstage/plugin-github-actions": "^0.3.5",
"@backstage/plugin-gitops-profiles": "^0.2.6",
"@backstage/plugin-graphiql": "^0.2.8",
"@backstage/plugin-jenkins": "^0.3.12",
"@backstage/plugin-kafka": "^0.2.5",
"@backstage/plugin-kubernetes": "^0.3.12",
"@backstage/plugin-lighthouse": "^0.2.13",
"@backstage/plugin-newrelic": "^0.2.6",
"@backstage/plugin-org": "^0.3.9",
"@backstage/plugin-pagerduty": "0.3.2",
"@backstage/plugin-register-component": "^0.2.12",
"@backstage/plugin-rollbar": "^0.3.3",
"@backstage/plugin-scaffolder": "^0.7.0",
"@backstage/plugin-sentry": "^0.3.8",
"@backstage/plugin-search": "^0.3.3",
"@backstage/plugin-tech-radar": "^0.3.7",
"@backstage/plugin-techdocs": "^0.6.0",
"@backstage/plugin-user-settings": "^0.2.7",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -41,7 +41,7 @@
"@roadiehq/backstage-plugin-buildkite": "^0.1.3",
"@roadiehq/backstage-plugin-github-insights": "^0.2.16",
"@roadiehq/backstage-plugin-github-pull-requests": "^0.7.6",
"@roadiehq/backstage-plugin-travis-ci": "^0.2.8",
"@roadiehq/backstage-plugin-travis-ci": "^0.4.5",
"history": "^5.0.0",
"prop-types": "^15.7.2",
"react": "^16.12.0",
+5 -5
View File
@@ -20,9 +20,9 @@ import App from './App';
describe('App', () => {
it('should render', async () => {
Object.defineProperty(process.env, 'APP_CONFIG', {
configurable: true,
value: [
process.env = {
NODE_ENV: 'test',
APP_CONFIG: [
{
data: {
app: {
@@ -39,8 +39,8 @@ describe('App', () => {
},
context: 'test',
},
],
});
] as any,
};
const rendered = await renderWithEffects(<App />);
expect(rendered.baseElement).toBeInTheDocument();
+4 -1
View File
@@ -33,7 +33,7 @@ import {
CostInsightsPage,
CostInsightsProjectGrowthInstructionsPage,
} from '@backstage/plugin-cost-insights';
import { ExplorePage } from '@backstage/plugin-explore';
import { ExplorePage, explorePlugin } from '@backstage/plugin-explore';
import { GcpProjectsPage } from '@backstage/plugin-gcp-projects';
import { GraphiQLPage } from '@backstage/plugin-graphiql';
import { LighthousePage } from '@backstage/plugin-lighthouse';
@@ -79,6 +79,9 @@ const app = createApp({
bind(apiDocsPlugin.externalRoutes, {
createComponent: scaffolderPlugin.routes.root,
});
bind(explorePlugin.externalRoutes, {
catalogEntity: catalogPlugin.routes.catalogEntity,
});
},
});
+7
View File
@@ -1,5 +1,12 @@
# @backstage/backend-common
## 0.5.5
### Patch Changes
- 497859088: Add optional `logClientErrors` to errorHandler to log 4XX errors
- 8adb48df4: Change debug log format to print as color grey
## 0.5.4
### Patch Changes
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-common",
"description": "Common functionality library for Backstage backends",
"version": "0.5.4",
"version": "0.5.5",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -68,7 +68,7 @@
}
},
"devDependencies": {
"@backstage/cli": "^0.6.1",
"@backstage/cli": "^0.6.3",
"@backstage/test-utils": "^0.1.7",
"@types/archiver": "^5.1.0",
"@types/compression": "^1.7.0",
+32
View File
@@ -1,5 +1,37 @@
# example-backend
## 0.2.18
### Patch Changes
- Updated dependencies [12d8f27a6]
- Updated dependencies [52b5bc3e2]
- Updated dependencies [ecdd407b1]
- Updated dependencies [4fbc9df79]
- Updated dependencies [12d8f27a6]
- Updated dependencies [497859088]
- Updated dependencies [1987c9341]
- Updated dependencies [f31b76b44]
- Updated dependencies [15eee03bc]
- Updated dependencies [f43192207]
- Updated dependencies [8adb48df4]
- Updated dependencies [e3adec2bd]
- Updated dependencies [9ce68b677]
- Updated dependencies [8106c9528]
- Updated dependencies [d0ed25196]
- Updated dependencies [96ccc8f69]
- Updated dependencies [3af994c81]
- @backstage/plugin-scaffolder-backend@0.9.0
- @backstage/plugin-techdocs-backend@0.6.3
- @backstage/plugin-catalog-backend@0.6.4
- @backstage/plugin-kafka-backend@0.2.1
- @backstage/catalog-model@0.7.3
- @backstage/backend-common@0.5.5
- @backstage/plugin-proxy-backend@0.2.5
- @backstage/plugin-auth-backend@0.3.3
- @backstage/plugin-kubernetes-backend@0.2.8
- example-app@0.2.18
## 0.2.17
### Patch Changes
+12 -12
View File
@@ -1,6 +1,6 @@
{
"name": "example-backend",
"version": "0.2.17",
"version": "0.2.18",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -27,25 +27,25 @@
"migrate:create": "knex migrate:make -x ts"
},
"dependencies": {
"@backstage/backend-common": "^0.5.3",
"@backstage/backend-common": "^0.5.5",
"@backstage/catalog-client": "^0.3.6",
"@backstage/catalog-model": "^0.7.2",
"@backstage/catalog-model": "^0.7.3",
"@backstage/config": "^0.1.3",
"@backstage/plugin-app-backend": "^0.3.8",
"@backstage/plugin-auth-backend": "^0.3.2",
"@backstage/plugin-catalog-backend": "^0.6.3",
"@backstage/plugin-auth-backend": "^0.3.3",
"@backstage/plugin-catalog-backend": "^0.6.4",
"@backstage/plugin-graphql-backend": "^0.1.5",
"@backstage/plugin-kubernetes-backend": "^0.2.7",
"@backstage/plugin-kafka-backend": "^0.2.0",
"@backstage/plugin-proxy-backend": "^0.2.4",
"@backstage/plugin-kubernetes-backend": "^0.2.8",
"@backstage/plugin-kafka-backend": "^0.2.1",
"@backstage/plugin-proxy-backend": "^0.2.5",
"@backstage/plugin-rollbar-backend": "^0.1.7",
"@backstage/plugin-scaffolder-backend": "^0.8.0",
"@backstage/plugin-techdocs-backend": "^0.6.2",
"@backstage/plugin-scaffolder-backend": "^0.9.0",
"@backstage/plugin-techdocs-backend": "^0.6.3",
"@gitbeaker/node": "^28.0.2",
"@octokit/rest": "^18.0.12",
"azure-devops-node-api": "^10.1.1",
"dockerode": "^3.2.1",
"example-app": "^0.2.17",
"example-app": "^0.2.18",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
"knex": "^0.21.6",
@@ -55,7 +55,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.6.2",
"@backstage/cli": "^0.6.3",
"@types/dockerode": "^3.2.1",
"@types/express": "^4.17.6",
"@types/express-serve-static-core": "^4.17.5"
+6
View File
@@ -1,5 +1,11 @@
# @backstage/catalog-model
## 0.7.3
### Patch Changes
- 12d8f27a6: Add version `backstage.io/v1beta2` schema for Template entities.
## 0.7.2
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-model",
"version": "0.7.2",
"version": "0.7.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -39,7 +39,7 @@
"yup": "^0.29.3"
},
"devDependencies": {
"@backstage/cli": "^0.6.2",
"@backstage/cli": "^0.6.3",
"@types/express": "^4.17.6",
"@types/jest": "^26.0.7",
"@types/lodash": "^4.14.151",
+11
View File
@@ -1,5 +1,16 @@
# @backstage/cli
## 0.6.3
### Patch Changes
- 507513fed: Bump `@svgr/webpack` from `5.4.x` to `5.5.x`.
- e37d2de99: Bump `@testing-library/react` in the plugin template from `^10.4.1` to `^11.2.5`. To apply this to an existing plugin, update the dependency in your `package.json`.
- 11c6208fe: Fixed an issue where the `backend:dev` command would get stuck executing the backend process multiple times, causing port conflict issues.
- d4f0a1406: New config command to export the configuration schema. When running backstage-cli with yarn, consider using `yarn --silent backstage-cli config:schema` to get a clean output on `stdout`.
- b93538acc: Fix for type declaration input being required for build even if types aren't being built.
- 8871e7523: Bump `ts-loader` dependency range from `^7.0.4` to `^8.0.17`.
## 0.6.2
### Patch Changes
+6 -6
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/cli",
"description": "CLI for developing Backstage plugins and apps",
"version": "0.6.2",
"version": "0.6.3",
"private": false,
"publishConfig": {
"access": "public"
@@ -106,7 +106,7 @@
"tar": "^6.0.1",
"terser-webpack-plugin": "^1.4.3",
"ts-jest": "^26.4.3",
"ts-loader": "^7.0.4",
"ts-loader": "^8.0.17",
"typescript": "^4.0.3",
"url-loader": "^4.1.0",
"webpack": "^4.41.6",
@@ -117,13 +117,13 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/backend-common": "^0.5.4",
"@backstage/backend-common": "^0.5.5",
"@backstage/config": "^0.1.3",
"@backstage/core": "^0.6.3",
"@backstage/dev-utils": "^0.1.12",
"@backstage/core": "^0.7.0",
"@backstage/dev-utils": "^0.1.13",
"@backstage/test-utils": "^0.1.8",
"@backstage/theme": "^0.2.3",
"@types/diff": "^4.0.2",
"@types/diff": "^5.0.0",
"@types/express": "^4.17.6",
"@types/fs-extra": "^9.0.1",
"@types/html-webpack-plugin": "^3.2.2",
+7
View File
@@ -14,10 +14,17 @@
* limitations under the License.
*/
import fs from 'fs-extra';
import { Command } from 'commander';
import { paths } from '../../lib/paths';
import { serveBackend } from '../../lib/bundler/backend';
export default async (cmd: Command) => {
// Cleaning dist/ before we start the dev process helps work around an issue
// where we end up with the entrypoint executing multiple times, causing
// a port bind conflict among other things.
await fs.remove(paths.resolveTarget('dist'));
const waitForExit = await serveBackend({
entry: 'src/index',
checksEnabled: cmd.check,
+16 -16
View File
@@ -36,22 +36,6 @@ import { svgrTemplate } from '../svgrTemplate';
export const makeConfigs = async (
options: BuildOptions,
): Promise<RollupOptions[]> => {
const typesInput = paths.resolveTargetRoot(
'dist-types',
relativePath(paths.targetRoot, paths.targetDir),
'src/index.d.ts',
);
const declarationsExist = await fs.pathExists(typesInput);
if (!declarationsExist) {
const path = relativePath(paths.targetDir, typesInput);
throw new Error(
`No declaration files found at ${path}, be sure to run ${chalk.bgRed.white(
'yarn tsc',
)} to generate .d.ts files before packaging`,
);
}
const configs = new Array<RollupOptions>();
if (options.outputs.has(Output.cjs) || options.outputs.has(Output.esm)) {
@@ -112,6 +96,22 @@ export const makeConfigs = async (
}
if (options.outputs.has(Output.types)) {
const typesInput = paths.resolveTargetRoot(
'dist-types',
relativePath(paths.targetRoot, paths.targetDir),
'src/index.d.ts',
);
const declarationsExist = await fs.pathExists(typesInput);
if (!declarationsExist) {
const path = relativePath(paths.targetDir, typesInput);
throw new Error(
`No declaration files found at ${path}, be sure to run ${chalk.bgRed.white(
'yarn tsc',
)} to generate .d.ts files before packaging`,
);
}
configs.push({
input: typesInput,
output: {
@@ -1,11 +1,10 @@
import React from 'react';
import { render } from '@testing-library/react';
import { ExampleComponent } from './ExampleComponent';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { msw } from '@backstage/test-utils';
import { msw, renderInTestApp } from '@backstage/test-utils';
describe('ExampleComponent', () => {
const server = setupServer();
@@ -19,8 +18,8 @@ describe('ExampleComponent', () => {
);
});
it('should render', () => {
const rendered = render(
it('should render', async () => {
const rendered = await renderInTestApp(
<ThemeProvider theme={lightTheme}>
<ExampleComponent />
</ThemeProvider>,
+7
View File
@@ -1,5 +1,12 @@
# @backstage/core-api
## 0.2.12
### Patch Changes
- 40c0fdbaa: Added support for optional external route references. By setting `optional: true` when creating an `ExternalRouteRef` it is no longer a requirement to bind the route in the app. If the app isn't bound `useRouteRef` will return `undefined`.
- 2a271d89e: Internal refactor of how component data is access to avoid polluting components and make it possible to bridge across versions.
## 0.2.11
### Patch Changes
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-api",
"description": "Internal Core API used by Backstage plugins and apps",
"version": "0.2.11",
"version": "0.2.12",
"private": false,
"publishConfig": {
"access": "public",
@@ -42,7 +42,7 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@backstage/cli": "^0.6.2",
"@backstage/cli": "^0.6.3",
"@backstage/test-utils": "^0.1.8",
"@backstage/test-utils-core": "^0.1.1",
"@testing-library/jest-dom": "^5.10.1",
+91 -37
View File
@@ -50,23 +50,30 @@ describe('generateBoundRoutes', () => {
describe('Integration Test', () => {
const plugin1RouteRef = createRouteRef({ path: '/blah1', title: '' });
const plugin2RouteRef = createRouteRef({ path: '/blah2', title: '' });
const externalRouteRef = createExternalRouteRef({ id: '3' });
const optionalBarExternalRouteRef = createExternalRouteRef({
id: 'bar',
const plugin2RouteRef = createRouteRef({
path: '/blah2',
title: '',
params: ['x'],
});
const err = createExternalRouteRef({ id: 'err' });
const errParams = createExternalRouteRef({ id: 'errParams', params: ['x'] });
const errOptional = createExternalRouteRef({
id: 'errOptional',
optional: true,
});
const optionalBazExternalRouteRef = createExternalRouteRef({
id: 'baz',
const errParamsOptional = createExternalRouteRef({
id: 'errParamsOptional',
optional: true,
params: ['x'],
});
const plugin1 = createPlugin({
id: 'blob',
externalRoutes: {
foo: externalRouteRef,
bar: optionalBarExternalRouteRef,
baz: optionalBazExternalRouteRef,
err,
errParams,
errOptional,
errParamsOptional,
},
});
@@ -85,14 +92,19 @@ describe('Integration Test', () => {
createRoutableExtension({
component: () =>
Promise.resolve((_: PropsWithChildren<{ path?: string }>) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const externalLink = useRouteRef(externalRouteRef);
const barLink = useRouteRef(optionalBarExternalRouteRef);
const bazLink = useRouteRef(optionalBazExternalRouteRef);
const errLink = useRouteRef(err);
const errParamsLink = useRouteRef(errParams);
const errOptionalLink = useRouteRef(errOptional);
const errParamsOptionalLink = useRouteRef(errParamsOptional);
return (
<div>
Our routes are: {externalLink()}, bar: {barLink?.() ?? 'none'},
baz: {bazLink?.() ?? 'none'}
<span>err: {errLink()}</span>
<span>errParams: {errParamsLink({ x: 'a' })}</span>
<span>errOptional: {errOptionalLink?.() ?? '<none>'}</span>
<span>
errParamsOptional:{' '}
{errParamsOptionalLink?.({ x: 'b' }) ?? '<none>'}
</span>
</div>
);
}),
@@ -100,14 +112,14 @@ describe('Integration Test', () => {
}),
);
it('runs happy paths', async () => {
const components = {
NotFoundErrorPage: () => null,
BootErrorPage: () => null,
Progress: () => null,
Router: BrowserRouter,
};
const components = {
NotFoundErrorPage: () => null,
BootErrorPage: () => null,
Progress: () => null,
Router: BrowserRouter,
};
it('runs happy paths', async () => {
const app = new PrivateAppImpl({
apis: [],
defaultApis: [],
@@ -124,8 +136,10 @@ describe('Integration Test', () => {
components,
bindRoutes: ({ bind }) => {
bind(plugin1.externalRoutes, {
foo: plugin2RouteRef,
bar: plugin2RouteRef,
err: plugin1RouteRef,
errParams: plugin2RouteRef,
errOptional: plugin1RouteRef,
errParamsOptional: plugin2RouteRef,
});
},
});
@@ -138,25 +152,19 @@ describe('Integration Test', () => {
<Router>
<Routes>
<ExposedComponent path="/" />
<HiddenComponent path="/foo/bar" />
<HiddenComponent path="/foo" />
</Routes>
</Router>
</Provider>,
);
expect(
screen.getByText('Our routes are: /foo/bar, bar: /foo/bar, baz: none'),
).toBeInTheDocument();
expect(screen.getByText('err: /')).toBeInTheDocument();
expect(screen.getByText('errParams: /foo')).toBeInTheDocument();
expect(screen.getByText('errOptional: /')).toBeInTheDocument();
expect(screen.getByText('errParamsOptional: /foo')).toBeInTheDocument();
});
it('should throw some error when the route has duplicate params', () => {
const components = {
NotFoundErrorPage: () => null,
BootErrorPage: () => null,
Progress: () => null,
Router: BrowserRouter,
};
it('runs happy paths without optional routes', async () => {
const app = new PrivateAppImpl({
apis: [],
defaultApis: [],
@@ -172,7 +180,53 @@ describe('Integration Test', () => {
plugins: [],
components,
bindRoutes: ({ bind }) => {
bind(plugin1.externalRoutes, { foo: plugin2RouteRef });
bind(plugin1.externalRoutes, {
err: plugin1RouteRef,
errParams: plugin2RouteRef,
});
},
});
const Provider = app.getProvider();
const Router = app.getRouter();
await renderWithEffects(
<Provider>
<Router>
<Routes>
<ExposedComponent path="/" />
<HiddenComponent path="/foo" />
</Routes>
</Router>
</Provider>,
);
expect(screen.getByText('err: /')).toBeInTheDocument();
expect(screen.getByText('errParams: /foo')).toBeInTheDocument();
expect(screen.getByText('errOptional: <none>')).toBeInTheDocument();
expect(screen.getByText('errParamsOptional: <none>')).toBeInTheDocument();
});
it('should throw some error when the route has duplicate params', () => {
const app = new PrivateAppImpl({
apis: [],
defaultApis: [],
themes: [
{
id: 'light',
title: 'Light Theme',
variant: 'light',
theme: lightTheme,
},
],
icons: defaultSystemIcons,
plugins: [],
components,
bindRoutes: ({ bind }) => {
bind(plugin1.externalRoutes, {
err: plugin1RouteRef,
errParams: plugin2RouteRef,
});
},
});
+3 -3
View File
@@ -50,14 +50,14 @@ import {
} from '../extensions/traversal';
import { IconComponent, IconComponentMap, IconKey } from '../icons';
import { BackstagePlugin } from '../plugin';
import { RouteRef } from '../routing';
import { AnyRoutes } from '../plugin/types';
import { RouteRef, ExternalRouteRef } from '../routing';
import {
routeObjectCollector,
routeParentCollector,
routePathCollector,
} from '../routing/collectors';
import { RoutingProvider, validateRoutes } from '../routing/hooks';
import { ExternalRouteRef } from '../routing/RouteRef';
import { AppContextProvider } from './AppContext';
import { AppIdentity } from './AppIdentity';
import { AppThemeProvider } from './AppThemeProvider';
@@ -78,7 +78,7 @@ export function generateBoundRoutes(
const result = new Map<ExternalRouteRef, RouteRef>();
if (bindRoutes) {
const bind: AppRouteBinder = (externalRoutes, targetRoutes) => {
const bind: AppRouteBinder = (externalRoutes, targetRoutes: AnyRoutes) => {
for (const [key, value] of Object.entries(targetRoutes)) {
const externalRoute = externalRoutes[key];
if (!externalRoute) {
+21 -31
View File
@@ -16,7 +16,7 @@
import { ComponentType } from 'react';
import { IconComponent, IconComponentMap, IconKey } from '../icons';
import { BackstagePlugin } from '../plugin/types';
import { AnyExternalRoutes, BackstagePlugin } from '../plugin/types';
import { ExternalRouteRef, RouteRef } from '../routing';
import { AnyApiFactory } from '../apis';
import { AppTheme, ProfileInfo } from '../apis/definitions';
@@ -79,49 +79,39 @@ export type AppComponents = {
*/
export type AppConfigLoader = () => Promise<AppConfig[]>;
/**
* Extracts the Optional type of a map of ExternalRouteRefs, leaving only the boolean in place as value
*/
type ExternalRouteRefsToOptionalMap<
T extends { [name in string]: ExternalRouteRef<boolean> }
> = {
[name in keyof T]: T[name] extends ExternalRouteRef<infer U> ? U : never;
};
/**
* Extracts a union of the keys in a map whose value extends the given type
*/
type ExtractKeysWithType<Obj extends { [key in string]: any }, Type> = {
type KeysWithType<Obj extends { [key in string]: any }, Type> = {
[key in keyof Obj]: Obj[key] extends Type ? key : never;
}[keyof Obj];
/**
* Given a map of boolean values denoting whether a route is optional, create a
* map of needed RouteRefs.
*
* For example { foo: false, bar: true } gives { foo: RouteRef<any>, bar?: RouteRef<any> }
* Takes a map Map required values and makes all keys matching Keys optional
*/
type CombineOptionalAndRequiredRoutes<
OptionalMap extends { [key in string]: boolean }
> = {
[name in ExtractKeysWithType<OptionalMap, false>]: RouteRef<any>;
} &
{ [name in keyof OptionalMap]?: RouteRef<any> };
type PartialKeys<
Map extends { [name in string]: any },
Keys extends keyof Map
> = Partial<Pick<Map, Keys>> & Required<Omit<Map, Keys>>;
/**
* Creates a map of required target routes based on whether the input external
* routes are optional or not. The external routes that are marked as optional
* will also be optional in the target routes map.
* Creates a map of target routes with matching parameters based on a map of external routes.
*/
type TargetRoutesMap<
T extends { [name in string]: ExternalRouteRef<boolean> }
> = CombineOptionalAndRequiredRoutes<ExternalRouteRefsToOptionalMap<T>>;
type TargetRouteMap<ExternalRoutes extends AnyExternalRoutes> = {
[name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef<
infer Params,
any
>
? RouteRef<Params>
: never;
};
export type AppRouteBinder = <
ExternalRoutes extends { [name in string]: ExternalRouteRef<boolean> }
>(
export type AppRouteBinder = <ExternalRoutes extends AnyExternalRoutes>(
externalRoutes: ExternalRoutes,
targetRoutes: TargetRoutesMap<ExternalRoutes>,
targetRoutes: PartialKeys<
TargetRouteMap<ExternalRoutes>,
KeysWithType<ExternalRoutes, ExternalRouteRef<any, true>>
>,
) => void;
export type AppOptions = {
@@ -100,19 +100,16 @@ describe('elementData', () => {
const element = <Component />;
const container = (global as any)[
'__@backstage/component-data-store__'
].store.get(element.type);
].get(element.type);
expect(container.map.get('my-data')).toBe(data);
});
it('should should be able to attach data for newer versions', () => {
const data = { foo: 'bar' };
const Component = () => null;
(global as any)['__@backstage/component-data-store__'].store.set(
Component,
{
map: new Map([['my-data', data]]),
},
);
(global as any)['__@backstage/component-data-store__'].set(Component, {
map: new Map([['my-data', data]]),
});
const element = <Component />;
expect(getComponentData(element, 'my-data')).toBe(data);
@@ -33,9 +33,10 @@ type MaybeComponentNode = ReactNode & {
};
// The store is bridged across versions using the global object
const { store } = getGlobalSingleton('component-data-store', () => ({
store: new WeakMap<ComponentType<any>, DataContainer>(),
}));
const store = getGlobalSingleton(
'component-data-store',
() => new WeakMap<ComponentType<any>, DataContainer>(),
);
export function attachComponentData<P>(
component: ComponentType<P>,
+1 -2
View File
@@ -15,9 +15,8 @@
*/
import { ComponentType } from 'react';
import { RouteRef } from '../routing';
import { RouteRef, ExternalRouteRef } from '../routing';
import { AnyApiFactory } from '../apis/system';
import { ExternalRouteRef } from '../routing/RouteRef';
export type RouteOptions = {
// Whether the route path must match exactly, defaults to true.
+65 -28
View File
@@ -14,19 +14,40 @@
* limitations under the License.
*/
import { RouteRef } from './types';
import {
RouteRef,
ExternalRouteRef,
routeRefType,
AnyParams,
ParamKeys,
} from './types';
import { IconComponent } from '../icons';
export type RouteRefConfig<Params extends { [param in string]: string }> = {
params?: Array<keyof Params>;
/** @deprecated Route refs no longer decide their own path */
// TODO(Rugvip): Remove this once we get rid of the deprecated fields, it's not exported
export type RouteRefConfig<Params extends AnyParams> = {
params?: ParamKeys<Params>;
path?: string;
icon?: IconComponent;
title: string;
};
export class AbsoluteRouteRef<Params extends { [param in string]: string }> {
constructor(private readonly config: RouteRefConfig<Params>) {}
class RouteRefBase {
constructor(type: string, id: string) {
this.toString = () => `routeRef{type=${type},id=${id}}`;
}
}
export class RouteRefImpl<Params extends AnyParams> extends RouteRefBase
implements RouteRef<Params> {
readonly [routeRefType] = 'absolute';
constructor(private readonly config: RouteRefConfig<Params>) {
super('absolute', config.title);
}
get params(): ParamKeys<Params> {
return this.config.params as any;
}
get icon() {
return this.config.icon;
@@ -40,12 +61,12 @@ export class AbsoluteRouteRef<Params extends { [param in string]: string }> {
get title() {
return this.config.title;
}
toString() {
return `routeRef{title=${this.title}}`;
}
}
type OptionalParams<
Params extends { [param in string]: string }
> = Params[keyof Params] extends never ? undefined : Params;
export function createRouteRef<
// Params is the type that we care about and the one to be embedded in the route ref.
// For example, given the params ['name', 'kind'], Params will be {name: string, kind: string}
@@ -58,27 +79,47 @@ export function createRouteRef<
params?: ParamKey[];
/** @deprecated Route refs no longer decide their own path */
path?: string;
/** @deprecated Route refs no longer decide their own icon */
icon?: IconComponent;
/** @deprecated Route refs no longer decide their own title */
title: string;
}): RouteRef<Params> {
return new AbsoluteRouteRef<Params>(config);
}): RouteRef<OptionalParams<Params>> {
return new RouteRefImpl<OptionalParams<Params>>({
...config,
params: (config.params ?? []) as ParamKeys<OptionalParams<Params>>,
});
}
export class ExternalRouteRef<Optional extends boolean = true> {
readonly optional: boolean;
export class ExternalRouteRefImpl<
Params extends AnyParams,
Optional extends boolean
> extends RouteRefBase implements ExternalRouteRef<Params, Optional> {
readonly [routeRefType] = 'external';
private constructor({ id, optional }: ExternalRouteRefOptions<Optional>) {
this.toString = () => `externalRouteRef{${id}}`;
this.optional = Boolean(optional);
constructor(
id: string,
readonly params: ParamKeys<Params>,
readonly optional: Optional,
) {
super('external', id);
}
}
export type ExternalRouteRefOptions<Optional extends boolean = false> = {
export function createExternalRouteRef<
Params extends { [param in ParamKey]: string },
Optional extends boolean = false,
ParamKey extends string = never
>(options: {
/**
* An identifier for this route, used to identify it in error messages
*/
id: string;
/**
* The parameters that will be provided to the external route reference.
*/
params?: ParamKey[];
/**
* Whether or not this route is optional, defaults to false.
*
@@ -86,14 +127,10 @@ export type ExternalRouteRefOptions<Optional extends boolean = false> = {
* if they aren't, `useRouteRef` will return `undefined`.
*/
optional?: Optional;
};
export function createExternalRouteRef<Optional extends boolean = false>(
options: ExternalRouteRefOptions<Optional>,
): ExternalRouteRef<Optional> {
return new ((ExternalRouteRef as unknown) as {
new (options: ExternalRouteRefOptions<Optional>): ExternalRouteRef<
Optional
>;
})(options);
}): ExternalRouteRef<OptionalParams<Params>, Optional> {
return new ExternalRouteRefImpl<OptionalParams<Params>, Optional>(
options.id,
(options.params ?? []) as ParamKeys<OptionalParams<Params>>,
Boolean(options.optional) as Optional,
);
}
+11 -5
View File
@@ -38,10 +38,9 @@ import {
import {
createRouteRef,
createExternalRouteRef,
ExternalRouteRef,
RouteRefConfig,
} from './RouteRef';
import { AnyRouteRef, RouteRef } from './types';
import { AnyRouteRef, RouteRef, ExternalRouteRef } from './types';
const mockConfig = (extra?: Partial<RouteRefConfig<{}>>) => ({
path: '/unused',
@@ -58,12 +57,19 @@ const ref1 = createRouteRef(mockConfig({ path: '/wat1' }));
const ref2 = createRouteRef(mockConfig({ path: '/wat2' }));
const ref3 = createRouteRef(mockConfig({ path: '/wat3' }));
const ref4 = createRouteRef(mockConfig({ path: '/wat4' }));
const ref5 = createRouteRef(mockConfig({ path: '/wat5' }));
const ref5 = createRouteRef({
...mockConfig({ path: '/wat5' }),
params: ['x'],
});
const eRefA = createExternalRouteRef({ id: '1' });
const eRefB = createExternalRouteRef({ id: '2' });
const eRefC = createExternalRouteRef({ id: '3' });
const eRefC = createExternalRouteRef({ id: '3', params: ['y'] });
const eRefD = createExternalRouteRef({ id: '4', optional: true });
const eRefE = createExternalRouteRef({ id: '5', optional: true });
const eRefE = createExternalRouteRef({
id: '5',
optional: true,
params: ['z'],
});
const MockRouteSource = <T extends { [name in string]: string }>(props: {
path?: string;
+17 -14
View File
@@ -15,19 +15,22 @@
*/
import React, { createContext, ReactNode, useContext, useMemo } from 'react';
import { AnyRouteRef, BackstageRouteObject, RouteRef } from './types';
import {
AnyRouteRef,
BackstageRouteObject,
RouteRef,
ExternalRouteRef,
AnyParams,
} from './types';
import { generatePath, matchRoutes, useLocation } from 'react-router-dom';
import { ExternalRouteRef } from './RouteRef';
// The extra TS magic here is to require a single params argument if the RouteRef
// had at least one param defined, but require 0 arguments if there are no params defined.
// Without this we'd have to pass in empty object to all parameter-less RouteRefs
// just to make TypeScript happy, or we would have to make the argument optional in
// which case you might forget to pass it in when it is actually required.
export type RouteFunc<Params extends { [param in string]: string }> = (
...[params]: Params[keyof Params] extends never
? readonly []
: readonly [Params]
export type RouteFunc<Params extends AnyParams> = (
...[params]: Params extends undefined ? readonly [] : readonly [Params]
) => string;
class RouteResolver {
@@ -38,8 +41,8 @@ class RouteResolver {
private readonly routeBindings: Map<RouteRef | ExternalRouteRef, RouteRef>,
) {}
resolve<Params extends { [param in string]: string }>(
routeRefOrExternalRouteRef: RouteRef<Params> | ExternalRouteRef,
resolve<Params extends AnyParams>(
routeRefOrExternalRouteRef: RouteRef<Params> | ExternalRouteRef<Params>,
sourceLocation: ReturnType<typeof useLocation>,
): RouteFunc<Params> | undefined {
const routeRef =
@@ -112,14 +115,14 @@ class RouteResolver {
const RoutingContext = createContext<RouteResolver | undefined>(undefined);
export function useRouteRef<Optional extends boolean>(
routeRef: ExternalRouteRef<Optional>,
): Optional extends true ? RouteFunc<{}> | undefined : RouteFunc<{}>;
export function useRouteRef<Params extends { [param in string]: string } = {}>(
export function useRouteRef<Optional extends boolean, Params extends AnyParams>(
routeRef: ExternalRouteRef<Params, Optional>,
): Optional extends true ? RouteFunc<Params> | undefined : RouteFunc<Params>;
export function useRouteRef<Params extends AnyParams>(
routeRef: RouteRef<Params>,
): RouteFunc<Params>;
export function useRouteRef<Params extends { [param in string]: string } = {}>(
routeRef: RouteRef<Params> | ExternalRouteRef,
export function useRouteRef<Params extends AnyParams>(
routeRef: RouteRef<Params> | ExternalRouteRef<Params, any>,
): RouteFunc<Params> | undefined {
const sourceLocation = useLocation();
const resolver = useContext(RoutingContext);
+2 -5
View File
@@ -19,12 +19,9 @@ export type {
AbsoluteRouteRef,
ConcreteRoute,
MutableRouteRef,
ExternalRouteRef,
} from './types';
export { FlatRoutes } from './FlatRoutes';
export {
createRouteRef,
createExternalRouteRef,
ExternalRouteRef,
} from './RouteRef';
export { createRouteRef, createExternalRouteRef } from './RouteRef';
export type { RouteRefConfig } from './RouteRef';
export { useRouteRef } from './hooks';
+36 -20
View File
@@ -15,35 +15,51 @@
*/
import { IconComponent } from '../icons';
import { ExternalRouteRef } from './RouteRef';
import { getGlobalSingleton } from '../lib/globalObject';
// @ts-ignore, we're just embedding the Params type for usage in other places
export type RouteRef<Params extends { [param in string]: string } = {}> = {
// TODO(Rugvip): Remove path, look up via registry instead
export type AnyParams = { [param in string]: string } | undefined;
export type ParamKeys<Params extends AnyParams> = keyof Params extends never
? []
: (keyof Params)[];
export const routeRefType: unique symbol = getGlobalSingleton<any>(
'route-ref-type',
() => Symbol('route-ref-type'),
);
export type RouteRef<Params extends AnyParams = any> = {
readonly [routeRefType]: 'absolute';
params: ParamKeys<Params>;
// TODO(Rugvip): Remove all of these once plugins don't rely on the path
/** @deprecated paths are no longer accessed directly from RouteRefs, use useRouteRef instead */
path: string;
/** @deprecated icons are no longer accessed via RouteRefs */
icon?: IconComponent;
title: string;
/** @deprecated titles are no longer accessed via RouteRefs */
title?: string;
};
export type AnyRouteRef = RouteRef<any> | ExternalRouteRef<any>;
export type ExternalRouteRef<
Params extends AnyParams = any,
Optional extends boolean = any
> = {
readonly [routeRefType]: 'external';
/**
* This type should not be used
* @deprecated
*/
params: ParamKeys<Params>;
optional?: Optional;
};
export type AnyRouteRef = RouteRef<any> | ExternalRouteRef<any, any>;
// TODO(Rugvip): None of these should be found in the wild anymore, remove in next minor release
/** @deprecated */
export type ConcreteRoute = {};
/**
* This type should not be used, use RouteRef instead
* @deprecated
*/
/** @deprecated */
export type AbsoluteRouteRef = RouteRef<{}>;
/**
* This type should not be used, use RouteRef instead
* @deprecated
*/
/** @deprecated */
export type MutableRouteRef = RouteRef<{}>;
// A duplicate of the react-router RouteObject, but with routeRef added
+43
View File
@@ -1,5 +1,48 @@
# @backstage/core
## 0.7.0
### Minor Changes
- 4c049a1a1: - Adds onClick and other props to IconLinkVertical;
- Allows TriggerButton component to render when pager duty key is missing;
- Refactors TriggerButton and PagerDutyCard not to have shared state;
- Removes the `action` prop of the IconLinkVertical component while adding `onClick`.
Instead of having an action including a button with onClick, now the whole component can be clickable making it easier to implement and having a better UX.
Before:
```ts
const myLink: IconLinkVerticalProps = {
label: 'Click me',
action: <Button onClick={myAction} />,
icon: <MyIcon onClick={myAction} />,
};
```
After:
```ts
const myLink: IconLinkVerticalProps = {
label: 'Click me',
onClick: myAction,
icon: <MyIcon />,
};
```
### Patch Changes
- 40c0fdbaa: Added support for optional external route references. By setting `optional: true` when creating an `ExternalRouteRef` it is no longer a requirement to bind the route in the app. If the app isn't bound `useRouteRef` will return `undefined`.
- 2a271d89e: Internal refactor of how component data is access to avoid polluting components and make it possible to bridge across versions.
- bece09057: Improve rendering of multiple support item links in the `SupportButton`
- 169f48deb: Added the color prop to TrendLine from the Sparklines props types to be able to have custom colors.
- 8a1566719: Added a new useSupportConfig hook that reads a new `app.support` config key. Also updated the SupportButton and ErrorPage components to use the new config.
- Updated dependencies [40c0fdbaa]
- Updated dependencies [2a271d89e]
- @backstage/core-api@0.2.12
## 0.6.3
### Patch Changes
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/core",
"description": "Core API used by Backstage plugins and apps",
"version": "0.6.3",
"version": "0.7.0",
"private": false,
"publishConfig": {
"access": "public",
@@ -30,7 +30,7 @@
},
"dependencies": {
"@backstage/config": "^0.1.3",
"@backstage/core-api": "^0.2.11",
"@backstage/core-api": "^0.2.12",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -68,7 +68,7 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@backstage/cli": "^0.6.2",
"@backstage/cli": "^0.6.3",
"@backstage/test-utils": "^0.1.8",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
@@ -65,14 +65,14 @@ const SupportListItem = ({ item }: { item: SupportItem }) => {
</ListItemIcon>
<ListItemText
primary={item.title}
secondary={
<>
{item.links &&
item.links.map(link => (
<SupportLink link={link} key={link.url} />
))}
</>
}
secondary={item.links?.reduce<React.ReactNodeArray>(
(prev, link, idx) => [
...prev,
idx > 0 && <br key={idx} />,
<SupportLink link={link} key={link.url} />,
],
[],
)}
/>
</ListItem>
);
@@ -87,8 +87,8 @@ export const Breadcrumbs = ({ children, ...props }: Props) => {
}}
>
<List>
{expandablePages.map(pageLink => (
<ListItem button>
{expandablePages.map((pageLink, index) => (
<ListItem key={index} button>
<StyledBox clone>{pageLink}</StyledBox>
</ListItem>
))}
@@ -13,86 +13,101 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Grid } from '@material-ui/core';
import {
Card,
CardActions,
CardContent,
CardMedia,
makeStyles,
Typography,
} from '@material-ui/core';
import React from 'react';
import { MemoryRouter } from 'react-router';
import { ItemCard } from '.';
import { Button } from '../../components';
import { ItemCardGrid } from './ItemCardGrid';
import { ItemCardHeader } from './ItemCardHeader';
export default {
title: 'Layout/Item Card',
component: ItemCard,
title: 'Layout/Item Cards',
};
const text =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
const useStyles = makeStyles({
grid: {
gridTemplateColumns: 'repeat(auto-fill, 12em)',
},
header: {
color: 'black',
backgroundImage: 'linear-gradient(to bottom right, red, yellow)',
},
});
export const Default = () => (
<Grid container spacing={4}>
<Grid item xs={12} md={3}>
<ItemCard
title="Item Card"
description="This is the description of an Item Card"
label="Button"
subtitle="Pretitle"
onClick={() => {}}
/>
</Grid>
<Grid item xs={12} md={3}>
<ItemCard
title="Item Card"
description="This is the description of an Item Card"
label="Button"
subtitle="Pretitle"
onClick={() => {}}
/>
</Grid>
</Grid>
);
export const Tags = () => (
<Grid container spacing={4}>
<Grid item xs={12} md={3}>
<ItemCard
title="Item Card"
description="This is the description of an Item Card with Tags"
tags={['one tag', 'one tag']}
label="Button"
/>
</Grid>
<Grid item xs={12} md={3}>
<ItemCard
title="Item Card"
description="This is the description of an Item Card with Tags"
tags={['one tag', 'two tag']}
label="Button"
/>
</Grid>
<Grid item xs={12} md={3}>
<ItemCard
title="Item Card"
description="This is the description of an Item Card without Tags"
label="Button"
/>
</Grid>
</Grid>
);
export const Link = () => (
<MemoryRouter>
<Grid container spacing={4}>
<Grid item xs={12} md={3}>
<ItemCard
title="Backstage"
description="This is the description of an Item Card with link"
label="Read More"
href="https://backstage.io"
/>
</Grid>
<Grid item xs={12} md={3}>
<ItemCard
title="Backstage @ GitHub"
description="This is the description of an Item Card with link"
label="Read More"
href="https://github.com/backstage/backstage"
/>
</Grid>
</Grid>
<Typography paragraph>
The most basic setup is to place a bunch of cards into a large grid,
leaving styling to the defaults. Try to resize the window to see how they
rearrange themselves to fit the viewport.
</Typography>
<ItemCardGrid>
{[...Array(10).keys()].map(index => (
<Card key={index}>
<CardMedia>
<ItemCardHeader title={`Card #${index}`} subtitle="Subtitle" />
</CardMedia>
<CardContent>
{text
.split(' ')
.slice(0, 5 + Math.floor(Math.random() * 30))
.join(' ')}
</CardContent>
<CardActions>
<Button color="primary" to="/catalog">
Go There!
</Button>
</CardActions>
</Card>
))}
</ItemCardGrid>
</MemoryRouter>
);
export const Styling = () => {
const classes = useStyles();
return (
<MemoryRouter>
<Typography paragraph>
Both the grid and the header can be styled, using the{' '}
<Typography variant="caption">classes</Typography> property. This lets
you for example tweak the column sizes and the background of the header.
</Typography>
<ItemCardGrid classes={{ root: classes.grid }}>
{[...Array(10).keys()].map(index => (
<Card key={index}>
<CardMedia>
<ItemCardHeader
title={`Card #${index}`}
subtitle="Subtitle"
classes={{ root: classes.header }}
/>
</CardMedia>
<CardContent>
{text
.split(' ')
.slice(0, 5 + Math.floor(Math.random() * 30))
.join(' ')}
</CardContent>
<CardActions>
<Button color="primary" to="/catalog">
Go There!
</Button>
</CardActions>
</Card>
))}
</ItemCardGrid>
</MemoryRouter>
);
};
+59 -60
View File
@@ -13,33 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Button, Card, Chip, makeStyles, Typography } from '@material-ui/core';
import clsx from 'clsx';
import {
Box,
Card,
CardActions,
CardContent,
CardMedia,
Chip,
} from '@material-ui/core';
import React, { ReactNode } from 'react';
import { Link } from '../../components';
const useStyles = makeStyles(theme => ({
header: {
color: theme.palette.common.white,
padding: theme.spacing(2, 2, 6),
backgroundImage: 'linear-gradient(-137deg, #4BB8A5 0%, #187656 100%)',
},
content: {
padding: theme.spacing(2),
},
description: {
height: 175,
overflow: 'hidden',
textOverflow: 'ellipsis',
},
withTags: {
height: 'calc(175px - 32px - 8px)',
},
footer: {
display: 'flex',
flexDirection: 'row-reverse',
},
}));
import { Button } from '../../components';
import { ItemCardHeader } from './ItemCardHeader';
type ItemCardProps = {
description?: string;
@@ -53,6 +37,31 @@ type ItemCardProps = {
href?: string;
};
/**
* This card type has been deprecated. Instead use plain MUI Card and helpers
* where appropriate.
*
* <code>
* <!--
* <Card>
* <CardMedia>
* <ItemCardHeader title="My Card" subtitle="neat!" />
* </CardMedia>
* <CardContent>
* Some text
* </CardContent>
* <CardActions>
* <Button color="primary" to="https://backstage.io">
* Get Started
* </Button>
* </CardActions>
* </Card>
* -->
* </code>
*
* @deprecated Use plain MUI <Card> and composable helpers instead.
* @see https://material-ui.com/components/cards/
*/
export const ItemCard = ({
description,
tags,
@@ -63,43 +72,33 @@ export const ItemCard = ({
onClick,
href,
}: ItemCardProps) => {
const classes = useStyles();
return (
<Card>
<div className={classes.header}>
{(subtitle || type) && (
<Typography variant="subtitle2">{subtitle ?? type}</Typography>
<CardMedia>
<ItemCardHeader title={title} subtitle={subtitle || type} />
</CardMedia>
<CardContent>
{tags?.length ? (
<Box>
{tags.map((tag, i) => (
<Chip size="small" label={tag} key={i} />
))}
</Box>
) : null}
{description}
</CardContent>
<CardActions>
{!href && (
<Button to="#" onClick={onClick} color="primary">
{label}
</Button>
)}
<Typography variant="h6">{title}</Typography>
</div>
<div className={classes.content}>
{tags?.map((tag, i) => (
<Chip label={tag} key={`tag-${i}`} />
))}
<Typography
variant="body2"
paragraph
className={clsx(
classes.description,
tags && tags.length > 0 && classes.withTags,
)}
>
{description}
</Typography>
<div className={classes.footer}>
{!href && (
<Button onClick={onClick} color="primary">
{label}
</Button>
)}
{href && (
<Button component={Link} to={href} color="primary">
{label}
</Button>
)}
</div>
</div>
{href && (
<Button to={href} color="primary">
{label}
</Button>
)}
</CardActions>
</Card>
);
};

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