This commit is contained in:
Esther Annorzie
2022-11-22 10:14:35 -05:00
203 changed files with 3115 additions and 1284 deletions
+11
View File
@@ -0,0 +1,11 @@
---
'@backstage/plugin-scaffolder': patch
---
Enabling the customization of the last step in the scaffolder template.
To override the content you have to do the next:
```typescript jsx
<TemplatePage ReviewStepComponent={YourCustomComponent} />
```
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/repo-tools': minor
---
Introducing repo-tools package
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-github': patch
---
Fixes the assignment of group member references in `GithubMultiOrgProcessor` so membership relations are resolved correctly.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder': patch
---
Initialize all `formData` in the `Stepper` in `/next`
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-org-react': patch
---
Bug fixes and adding the possibility to add a default value for the `GroupListPicker`. Fixes: Vertical size jump on text entry, left align for text, selecting a value closes the popup, auto focus on the popup when opening
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-cost-insights': patch
---
Provide the ability to change the base currency from USD to any other currency in cost insights plugin
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Bump `msw` version in default plugin/app templates
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-components': patch
---
Create a variable for minimum height and add a prop named 'fit' for determining if the graph height should grow or be contained.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-components': patch
---
Add items prop to SupportButton. This prop can be used to override the items that would otherwise be grabbed from the config.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-explore': patch
---
Adds styling to graph forcing it to always fill out the available space.
+193
View File
@@ -0,0 +1,193 @@
{
"mode": "pre",
"tag": "next",
"initialVersions": {
"example-app": "0.2.77",
"@backstage/app-defaults": "1.0.8",
"example-backend": "0.2.77",
"@backstage/backend-app-api": "0.2.3",
"@backstage/backend-common": "0.16.0",
"@backstage/backend-defaults": "0.1.3",
"example-backend-next": "0.0.5",
"@backstage/backend-plugin-api": "0.1.4",
"@backstage/backend-tasks": "0.3.7",
"@backstage/backend-test-utils": "0.1.30",
"@backstage/catalog-client": "1.1.2",
"@backstage/catalog-model": "1.1.3",
"@backstage/cli": "0.21.0",
"@backstage/cli-common": "0.1.10",
"@backstage/codemods": "0.1.41",
"@backstage/config": "1.0.4",
"@backstage/config-loader": "1.1.6",
"@backstage/core-app-api": "1.2.0",
"@backstage/core-components": "0.12.0",
"@backstage/core-plugin-api": "1.1.0",
"@backstage/create-app": "0.4.34",
"@backstage/dev-utils": "1.0.8",
"e2e-test": "0.2.0",
"@backstage/errors": "1.1.3",
"@backstage/integration": "1.4.0",
"@backstage/integration-react": "1.1.6",
"@backstage/release-manifests": "0.0.7",
"@backstage/repo-tools": "0.0.0",
"@techdocs/cli": "1.2.3",
"techdocs-cli-embedded-app": "0.2.76",
"@backstage/test-utils": "1.2.2",
"@backstage/theme": "0.2.16",
"@backstage/types": "1.0.1",
"@backstage/version-bridge": "1.0.2",
"@backstage/plugin-adr": "0.2.3",
"@backstage/plugin-adr-backend": "0.2.3",
"@backstage/plugin-adr-common": "0.2.3",
"@backstage/plugin-airbrake": "0.3.11",
"@backstage/plugin-airbrake-backend": "0.2.11",
"@backstage/plugin-allure": "0.1.27",
"@backstage/plugin-analytics-module-ga": "0.1.22",
"@backstage/plugin-apache-airflow": "0.2.4",
"@backstage/plugin-api-docs": "0.8.11",
"@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.0",
"@backstage/plugin-apollo-explorer": "0.1.4",
"@backstage/plugin-app-backend": "0.3.38",
"@backstage/plugin-auth-backend": "0.17.1",
"@backstage/plugin-auth-node": "0.2.7",
"@backstage/plugin-azure-devops": "0.2.2",
"@backstage/plugin-azure-devops-backend": "0.3.17",
"@backstage/plugin-azure-devops-common": "0.3.0",
"@backstage/plugin-azure-sites": "0.1.0",
"@backstage/plugin-azure-sites-backend": "0.1.0",
"@backstage/plugin-azure-sites-common": "0.1.0",
"@backstage/plugin-badges": "0.2.35",
"@backstage/plugin-badges-backend": "0.1.32",
"@backstage/plugin-bazaar": "0.2.0",
"@backstage/plugin-bazaar-backend": "0.2.1",
"@backstage/plugin-bitbucket-cloud-common": "0.2.1",
"@backstage/plugin-bitrise": "0.1.38",
"@backstage/plugin-catalog": "1.6.1",
"@backstage/plugin-catalog-backend": "1.5.1",
"@backstage/plugin-catalog-backend-module-aws": "0.1.11",
"@backstage/plugin-catalog-backend-module-azure": "0.1.9",
"@backstage/plugin-catalog-backend-module-bitbucket": "0.2.5",
"@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.1.5",
"@backstage/plugin-catalog-backend-module-bitbucket-server": "0.1.3",
"@backstage/plugin-catalog-backend-module-gerrit": "0.1.6",
"@backstage/plugin-catalog-backend-module-github": "0.2.0",
"@backstage/plugin-catalog-backend-module-gitlab": "0.1.9",
"@backstage/plugin-catalog-backend-module-ldap": "0.5.5",
"@backstage/plugin-catalog-backend-module-msgraph": "0.4.4",
"@backstage/plugin-catalog-backend-module-openapi": "0.1.4",
"@backstage/plugin-catalog-common": "1.0.8",
"@internal/plugin-catalog-customized": "0.0.4",
"@backstage/plugin-catalog-graph": "0.2.23",
"@backstage/plugin-catalog-graphql": "0.3.15",
"@backstage/plugin-catalog-import": "0.9.1",
"@backstage/plugin-catalog-node": "1.2.1",
"@backstage/plugin-catalog-react": "1.2.1",
"@backstage/plugin-cicd-statistics": "0.1.13",
"@backstage/plugin-cicd-statistics-module-gitlab": "0.1.7",
"@backstage/plugin-circleci": "0.3.11",
"@backstage/plugin-cloudbuild": "0.3.11",
"@backstage/plugin-code-climate": "0.1.11",
"@backstage/plugin-code-coverage": "0.2.4",
"@backstage/plugin-code-coverage-backend": "0.2.4",
"@backstage/plugin-codescene": "0.1.6",
"@backstage/plugin-config-schema": "0.1.34",
"@backstage/plugin-cost-insights": "0.12.0",
"@backstage/plugin-cost-insights-common": "0.1.1",
"@backstage/plugin-dynatrace": "1.0.1",
"@backstage/plugin-events-backend": "0.1.0",
"@backstage/plugin-events-backend-module-aws-sqs": "0.1.0",
"@backstage/plugin-events-backend-module-azure": "0.1.0",
"@backstage/plugin-events-backend-module-bitbucket-cloud": "0.1.0",
"@backstage/plugin-events-backend-module-gerrit": "0.1.0",
"@backstage/plugin-events-backend-module-github": "0.1.0",
"@backstage/plugin-events-backend-module-gitlab": "0.1.0",
"@backstage/plugin-events-backend-test-utils": "0.1.0",
"@backstage/plugin-events-node": "0.1.0",
"@internal/plugin-todo-list": "1.0.7",
"@internal/plugin-todo-list-backend": "1.0.7",
"@internal/plugin-todo-list-common": "1.0.6",
"@backstage/plugin-explore": "0.3.42",
"@backstage/plugin-explore-react": "0.0.23",
"@backstage/plugin-firehydrant": "0.1.28",
"@backstage/plugin-fossa": "0.2.43",
"@backstage/plugin-gcalendar": "0.3.7",
"@backstage/plugin-gcp-projects": "0.3.30",
"@backstage/plugin-git-release-manager": "0.3.24",
"@backstage/plugin-github-actions": "0.5.11",
"@backstage/plugin-github-deployments": "0.1.42",
"@backstage/plugin-github-issues": "0.2.0",
"@backstage/plugin-github-pull-requests-board": "0.1.5",
"@backstage/plugin-gitops-profiles": "0.3.29",
"@backstage/plugin-gocd": "0.1.17",
"@backstage/plugin-graphiql": "0.2.43",
"@backstage/plugin-graphql-backend": "0.1.28",
"@backstage/plugin-home": "0.4.27",
"@backstage/plugin-ilert": "0.2.0",
"@backstage/plugin-jenkins": "0.7.10",
"@backstage/plugin-jenkins-backend": "0.1.28",
"@backstage/plugin-jenkins-common": "0.1.10",
"@backstage/plugin-kafka": "0.3.11",
"@backstage/plugin-kafka-backend": "0.2.31",
"@backstage/plugin-kubernetes": "0.7.4",
"@backstage/plugin-kubernetes-backend": "0.8.0",
"@backstage/plugin-kubernetes-common": "0.4.4",
"@backstage/plugin-lighthouse": "0.3.11",
"@backstage/plugin-newrelic": "0.3.29",
"@backstage/plugin-newrelic-dashboard": "0.2.4",
"@backstage/plugin-org": "0.6.0",
"@backstage/plugin-org-react": "0.1.0",
"@backstage/plugin-pagerduty": "0.5.4",
"@backstage/plugin-periskop": "0.1.9",
"@backstage/plugin-periskop-backend": "0.1.9",
"@backstage/plugin-permission-backend": "0.5.13",
"@backstage/plugin-permission-common": "0.7.1",
"@backstage/plugin-permission-node": "0.7.1",
"@backstage/plugin-permission-react": "0.4.7",
"@backstage/plugin-playlist": "0.1.2",
"@backstage/plugin-playlist-backend": "0.2.1",
"@backstage/plugin-playlist-common": "0.1.2",
"@backstage/plugin-proxy-backend": "0.2.32",
"@backstage/plugin-rollbar": "0.4.11",
"@backstage/plugin-rollbar-backend": "0.1.35",
"@backstage/plugin-scaffolder": "1.8.0",
"@backstage/plugin-scaffolder-backend": "1.8.0",
"@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.13",
"@backstage/plugin-scaffolder-backend-module-rails": "0.4.6",
"@backstage/plugin-scaffolder-backend-module-yeoman": "0.2.11",
"@backstage/plugin-scaffolder-common": "1.2.2",
"@backstage/plugin-search": "1.0.4",
"@backstage/plugin-search-backend": "1.1.1",
"@backstage/plugin-search-backend-module-elasticsearch": "1.0.4",
"@backstage/plugin-search-backend-module-pg": "0.4.2",
"@backstage/plugin-search-backend-node": "1.0.4",
"@backstage/plugin-search-common": "1.1.1",
"@backstage/plugin-search-react": "1.2.1",
"@backstage/plugin-sentry": "0.4.4",
"@backstage/plugin-shortcuts": "0.3.3",
"@backstage/plugin-sonarqube": "0.5.0",
"@backstage/plugin-sonarqube-backend": "0.1.3",
"@backstage/plugin-splunk-on-call": "0.4.0",
"@backstage/plugin-stack-overflow": "0.1.7",
"@backstage/plugin-stack-overflow-backend": "0.1.7",
"@backstage/plugin-tech-insights": "0.3.3",
"@backstage/plugin-tech-insights-backend": "0.5.4",
"@backstage/plugin-tech-insights-backend-module-jsonfc": "0.1.22",
"@backstage/plugin-tech-insights-common": "0.2.8",
"@backstage/plugin-tech-insights-node": "0.3.6",
"@backstage/plugin-tech-radar": "0.5.18",
"@backstage/plugin-techdocs": "1.4.0",
"@backstage/plugin-techdocs-addons-test-utils": "1.0.6",
"@backstage/plugin-techdocs-backend": "1.4.1",
"@backstage/plugin-techdocs-module-addons-contrib": "1.0.6",
"@backstage/plugin-techdocs-node": "1.4.2",
"@backstage/plugin-techdocs-react": "1.0.6",
"@backstage/plugin-todo": "0.2.13",
"@backstage/plugin-todo-backend": "0.1.35",
"@backstage/plugin-user-settings": "0.5.1",
"@backstage/plugin-user-settings-backend": "0.1.2",
"@backstage/plugin-vault": "0.1.5",
"@backstage/plugin-vault-backend": "0.2.4",
"@backstage/plugin-xcmetrics": "0.2.31"
},
"changesets": []
}
+113
View File
@@ -0,0 +1,113 @@
---
'@backstage/backend-common': patch
'@backstage/backend-test-utils': patch
'@backstage/catalog-client': patch
'@backstage/cli': patch
'@backstage/config-loader': patch
'@backstage/core-app-api': patch
'@backstage/core-components': patch
'@backstage/core-plugin-api': patch
'@backstage/integration-react': patch
'@backstage/integration': patch
'@backstage/release-manifests': patch
'@backstage/test-utils': patch
'@backstage/plugin-adr-backend': patch
'@backstage/plugin-adr': patch
'@backstage/plugin-airbrake-backend': patch
'@backstage/plugin-airbrake': patch
'@backstage/plugin-allure': patch
'@backstage/plugin-analytics-module-ga': patch
'@backstage/plugin-apache-airflow': patch
'@backstage/plugin-api-docs': patch
'@backstage/plugin-apollo-explorer': patch
'@backstage/plugin-app-backend': patch
'@backstage/plugin-auth-backend': patch
'@backstage/plugin-auth-node': patch
'@backstage/plugin-azure-devops-backend': patch
'@backstage/plugin-azure-devops': patch
'@backstage/plugin-azure-sites-backend': patch
'@backstage/plugin-azure-sites': patch
'@backstage/plugin-badges': patch
'@backstage/plugin-bitbucket-cloud-common': patch
'@backstage/plugin-bitrise': patch
'@backstage/plugin-catalog-backend-module-azure': patch
'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch
'@backstage/plugin-catalog-backend-module-bitbucket-server': patch
'@backstage/plugin-catalog-backend-module-bitbucket': patch
'@backstage/plugin-catalog-backend-module-gerrit': patch
'@backstage/plugin-catalog-backend-module-github': patch
'@backstage/plugin-catalog-backend-module-gitlab': patch
'@backstage/plugin-catalog-backend-module-msgraph': patch
'@backstage/plugin-catalog-backend': patch
'@backstage/plugin-catalog-graphql': patch
'@backstage/plugin-catalog-import': patch
'@backstage/plugin-circleci': patch
'@backstage/plugin-cloudbuild': patch
'@backstage/plugin-code-climate': patch
'@backstage/plugin-code-coverage-backend': patch
'@backstage/plugin-code-coverage': patch
'@backstage/plugin-codescene': patch
'@backstage/plugin-config-schema': patch
'@backstage/plugin-cost-insights': patch
'@backstage/plugin-dynatrace': patch
'@backstage/plugin-explore-react': patch
'@backstage/plugin-explore': patch
'@backstage/plugin-firehydrant': patch
'@backstage/plugin-fossa': patch
'@backstage/plugin-gcalendar': patch
'@backstage/plugin-gcp-projects': patch
'@backstage/plugin-git-release-manager': patch
'@backstage/plugin-github-actions': patch
'@backstage/plugin-github-deployments': patch
'@backstage/plugin-github-issues': patch
'@backstage/plugin-github-pull-requests-board': patch
'@backstage/plugin-gitops-profiles': patch
'@backstage/plugin-gocd': patch
'@backstage/plugin-graphiql': patch
'@backstage/plugin-graphql-backend': patch
'@backstage/plugin-home': patch
'@backstage/plugin-ilert': patch
'@backstage/plugin-jenkins-backend': patch
'@backstage/plugin-jenkins': patch
'@backstage/plugin-kafka': patch
'@backstage/plugin-kubernetes': patch
'@backstage/plugin-lighthouse': patch
'@backstage/plugin-newrelic': patch
'@backstage/plugin-org-react': patch
'@backstage/plugin-org': patch
'@backstage/plugin-pagerduty': patch
'@backstage/plugin-periskop-backend': patch
'@backstage/plugin-periskop': patch
'@backstage/plugin-permission-backend': patch
'@backstage/plugin-permission-common': patch
'@backstage/plugin-permission-node': patch
'@backstage/plugin-playlist-backend': patch
'@backstage/plugin-playlist': patch
'@backstage/plugin-proxy-backend': patch
'@backstage/plugin-rollbar-backend': patch
'@backstage/plugin-rollbar': patch
'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch
'@backstage/plugin-scaffolder-backend': patch
'@backstage/plugin-scaffolder': patch
'@backstage/plugin-search': patch
'@backstage/plugin-sentry': patch
'@backstage/plugin-shortcuts': patch
'@backstage/plugin-sonarqube-backend': patch
'@backstage/plugin-sonarqube': patch
'@backstage/plugin-splunk-on-call': patch
'@backstage/plugin-stack-overflow': patch
'@backstage/plugin-tech-insights': patch
'@backstage/plugin-tech-radar': patch
'@backstage/plugin-techdocs-addons-test-utils': patch
'@backstage/plugin-techdocs-backend': patch
'@backstage/plugin-techdocs-module-addons-contrib': patch
'@backstage/plugin-techdocs': patch
'@backstage/plugin-todo-backend': patch
'@backstage/plugin-todo': patch
'@backstage/plugin-user-settings': patch
'@backstage/plugin-vault-backend': patch
'@backstage/plugin-vault': patch
'@backstage/plugin-xcmetrics': patch
---
Updated dependency `msw` to `^0.49.0`.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-kubernetes-backend': patch
'@backstage/plugin-kubernetes-common': patch
---
Added Kubernetes proxy API route to backend Kubernetes plugin, allowing Backstage plugin developers to read/write new information from Kubernetes (if proper credentials are provided).
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-search-backend-node': patch
---
Fixed a bug that could cause a `max listeners exceeded warning` to be logged when more than 10 collators were running simultaneously.
+2 -2
View File
@@ -1,6 +1,6 @@
name: Automate merge message
on:
pull_request:
pull_request_target:
branches: ['master']
types: ['closed']
@@ -25,7 +25,7 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
ref: '${{ github.event.pull_request.merge_commit_sha }}'
ref: '${{ github.event.pull_request.head.sha }}'
- name: fetch base
run: git fetch --depth 1 origin ${{ github.event.pull_request.base.sha }}
+5 -5
View File
@@ -9,13 +9,13 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
path: backstage
ref: v${{ github.event.client_payload.version }}
- name: setup-node
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: 16.x
registry-url: https://registry.npmjs.org/
@@ -23,7 +23,7 @@ jobs:
# Beginning of yarn setup, keep in sync between all workflows.
# TODO(Rugvip): move this to composite action once all features we use are supported
- name: use node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -31,7 +31,7 @@ jobs:
# Cache every node_modules folder inside the monorepo
- name: cache all node_modules
id: cache-modules
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: '**/node_modules'
# We use both yarn.lock and package.json as cache keys to ensure that
@@ -47,7 +47,7 @@ jobs:
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: cache global yarn cache
uses: actions/cache@v2
uses: actions/cache@v3
if: steps.cache-modules.outputs.cache-hit != 'true'
with:
path: ${{ steps.yarn-cache.outputs.dir }}
+6 -6
View File
@@ -1415,22 +1415,22 @@ __metadata:
linkType: hard
"typescript@npm:^4.1.3":
version: 4.8.4
resolution: "typescript@npm:4.8.4"
version: 4.9.3
resolution: "typescript@npm:4.9.3"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 3e4f061658e0c8f36c820802fa809e0fd812b85687a9a2f5430bc3d0368e37d1c9605c3ce9b39df9a05af2ece67b1d844f9f6ea8ff42819f13bcb80f85629af0
checksum: 17b8f816050b412403e38d48eef0e893deb6be522d6dc7caf105e54a72e34daf6835c447735fd2b28b66784e72bfbf87f627abb4818a8e43d1fa8106396128dc
languageName: node
linkType: hard
"typescript@patch:typescript@^4.1.3#~builtin<compat/typescript>":
version: 4.8.4
resolution: "typescript@patch:typescript@npm%3A4.8.4#~builtin<compat/typescript>::version=4.8.4&hash=a1c5e5"
version: 4.9.3
resolution: "typescript@patch:typescript@npm%3A4.9.3#~builtin<compat/typescript>::version=4.9.3&hash=a1c5e5"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 563a0ef47abae6df27a9a3ab38f75fc681f633ccf1a3502b1108e252e187787893de689220f4544aaf95a371a4eb3141e4a337deb9895de5ac3c1ca76430e5f0
checksum: ef65c22622d864497d0a0c5db693523329b3284c15fe632e93ad9aa059e8dc38ef3bd767d6f26b1e5ecf9446f49bd0f6c4e5714a2eeaf352805dc002479843d1
languageName: node
linkType: hard
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+4 -4
View File
@@ -141,14 +141,14 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3
- uses: actions/setup-node@v2
- uses: actions/setup-python@v2
- uses: actions/setup-node@v3
- uses: actions/setup-python@v4
# the 2 steps below can be removed if you aren't using plantuml in your documentation
- name: setup java
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: '11'
+92
View File
@@ -393,6 +393,98 @@ You can add more icons, if the [default icons](https://github.com/backstage/back
Note: If the icon is not available as one of the default icons or one you've added then it will fall back to Material UI's `LanguageIcon`
## Custom Sidebar
As you've seen there are many ways that you can customize your Backstage app. The following section will show you how you can customize the sidebar.
### Sidebar Sub-menu
For this example we'll show you how you can expand the sidebar with a sub-menu:
1. Open the `Root.tsx` file located in `packages/app/src/components/Root` as this is where the sidebar code lives
2. Then we want to add the following imports for the icons:
```ts
import ApiIcon from '@material-ui/icons/Extension';
import ComponentIcon from '@material-ui/icons/Memory';
import DomainIcon from '@material-ui/icons/Apartment';
import ResourceIcon from '@material-ui/icons/Work';
import SystemIcon from '@material-ui/icons/Category';
import UserIcon from '@material-ui/icons/Person';
```
3. Then update the `@backstage/core-components` import like this:
```diff
import {
Sidebar,
sidebarConfig,
SidebarDivider,
SidebarGroup,
SidebarItem,
SidebarPage,
SidebarScrollWrapper,
SidebarSpace,
useSidebarOpenState,
Link,
+ GroupIcon,
+ SidebarSubmenu,
+ SidebarSubmenuItem,
} from '@backstage/core-components';
```
4. Finally replace `<SidebarItem icon={HomeIcon} to="catalog" text="Home" />` with this:
```ts
<SidebarItem icon={HomeIcon} to="catalog" text="Home">
<SidebarSubmenu title="Catalog">
<SidebarSubmenuItem
title="Domains"
to="catalog?filters[kind]=domain"
icon={DomainIcon}
/>
<SidebarSubmenuItem
title="Systems"
to="catalog?filters[kind]=system"
icon={SystemIcon}
/>
<SidebarSubmenuItem
title="Components"
to="catalog?filters[kind]=component"
icon={ComponentIcon}
/>
<SidebarSubmenuItem
title="APIs"
to="catalog?filters[kind]=api"
icon={ApiIcon}
/>
<SidebarDivider />
<SidebarSubmenuItem
title="Resources"
to="catalog?filters[kind]=resource"
icon={ResourceIcon}
/>
<SidebarDivider />
<SidebarSubmenuItem
title="Groups"
to="catalog?filters[kind]=group"
icon={GroupIcon}
/>
<SidebarSubmenuItem
title="Users"
to="catalog?filters[kind]=user"
icon={UserIcon}
/>
</SidebarSubmenu>
</SidebarItem>
```
When you startup your Backstage app and hover over the Home option on the sidebar you'll now see a nice sub-menu appear with links to the various Kinds in your Catalog. It would look like this:
![Sidebar sub-menu example](./../assets/getting-started/sidebar-submenu-example.png)
You can see more ways to use this in the [Storybook Sidebar examples](https://backstage.io/storybook/?path=/story/layout-sidebar--sample-scalable-sidebar)
## Custom Homepage
In addition to a custom theme, a custom logo, you can also customize the
+3 -6
View File
@@ -11,7 +11,7 @@
"build:backend": "yarn workspace backend build",
"build:all": "backstage-cli repo build --all",
"build:api-reports": "yarn build:api-reports:only --tsc",
"build:api-reports:only": "ts-node -T -P scripts/tsconfig.json scripts/api-extractor.ts",
"build:api-reports:only": "backstage-repo-tools api-reports",
"build:api-docs": "LANG=en_EN yarn build:api-reports --docs",
"tsc": "tsc",
"tsc:full": "backstage-cli repo clean && tsc --skipLibCheck false --incremental false",
@@ -49,16 +49,13 @@
"version": "1.8.0",
"dependencies": {
"@backstage/errors": "workspace:^",
"@manypkg/get-packages": "^1.1.3",
"@microsoft/api-documenter": "^7.17.11",
"@microsoft/api-extractor": "^7.23.0",
"@microsoft/api-extractor-model": "^7.17.2",
"@microsoft/tsdoc": "^0.14.1"
"@manypkg/get-packages": "^1.1.3"
},
"devDependencies": {
"@backstage/cli": "workspace:*",
"@backstage/codemods": "workspace:*",
"@backstage/create-app": "workspace:*",
"@backstage/repo-tools": "workspace:*",
"@changesets/cli": "^2.14.0",
"@octokit/rest": "^19.0.3",
"@spotify/prettier-config": "^14.0.0",
+50 -1
View File
@@ -47,12 +47,21 @@ import {
SidebarSpace,
Link,
useSidebarOpenState,
SidebarSubmenu,
SidebarSubmenuItem,
} from '@backstage/core-components';
import { MyGroupsSidebarItem } from '@backstage/plugin-org';
import GroupIcon from '@material-ui/icons/People';
import { SearchModal } from '../search/SearchModal';
import Score from '@material-ui/icons/Score';
import ApiIcon from '@material-ui/icons/Extension';
import ComponentIcon from '@material-ui/icons/Memory';
import DomainIcon from '@material-ui/icons/Apartment';
import ResourceIcon from '@material-ui/icons/Work';
import SystemIcon from '@material-ui/icons/Category';
import UserIcon from '@material-ui/icons/Person';
const useSidebarLogoStyles = makeStyles({
root: {
width: sidebarConfig.drawerWidthClosed,
@@ -93,7 +102,47 @@ export const Root = ({ children }: PropsWithChildren<{}>) => (
<SidebarDivider />
<SidebarGroup label="Menu" icon={<MenuIcon />}>
{/* Global nav, not org-specific */}
<SidebarItem icon={HomeIcon} to="catalog" text="Home" />
<SidebarItem icon={HomeIcon} to="catalog" text="Home">
<SidebarSubmenu title="Catalog">
<SidebarSubmenuItem
title="Domains"
to="catalog?filters[kind]=domain"
icon={DomainIcon}
/>
<SidebarSubmenuItem
title="Systems"
to="catalog?filters[kind]=system"
icon={SystemIcon}
/>
<SidebarSubmenuItem
title="Components"
to="catalog?filters[kind]=component"
icon={ComponentIcon}
/>
<SidebarSubmenuItem
title="APIs"
to="catalog?filters[kind]=api"
icon={ApiIcon}
/>
<SidebarDivider />
<SidebarSubmenuItem
title="Resources"
to="catalog?filters[kind]=resource"
icon={ResourceIcon}
/>
<SidebarDivider />
<SidebarSubmenuItem
title="Groups"
to="catalog?filters[kind]=group"
icon={GroupIcon}
/>
<SidebarSubmenuItem
title="Users"
to="catalog?filters[kind]=user"
icon={UserIcon}
/>
</SidebarSubmenu>
</SidebarItem>
<MyGroupsSidebarItem
singularTitle="My Squad"
pluralTitle="My Squads"
@@ -19,7 +19,6 @@ import {
Content,
DocsIcon,
Header,
Lifecycle,
Page,
useSidebarPinState,
} from '@backstage/core-components';
@@ -66,7 +65,7 @@ const SearchPage = () => {
return (
<Page themeId="home">
{!isMobile && <Header title="Search" subtitle={<Lifecycle alpha />} />}
{!isMobile && <Header title="Search" />}
<Content>
<Grid container direction="row">
<Grid item xs={12}>
+1 -1
View File
@@ -117,7 +117,7 @@
"better-sqlite3": "^7.5.0",
"http-errors": "^2.0.0",
"mock-fs": "^5.1.0",
"msw": "^0.48.0",
"msw": "^0.49.0",
"mysql2": "^2.2.5",
"recursive-readdir": "^2.2.2",
"supertest": "^6.1.3"
+1 -1
View File
@@ -41,7 +41,7 @@
"@backstage/config": "workspace:^",
"better-sqlite3": "^7.5.0",
"knex": "^2.0.0",
"msw": "^0.48.0",
"msw": "^0.49.0",
"mysql2": "^2.2.5",
"pg": "^8.3.0",
"testcontainers": "^8.1.2",
+1 -1
View File
@@ -38,7 +38,7 @@
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -155,7 +155,7 @@
"@types/yarnpkg__lockfile": "^1.1.4",
"del": "^6.0.0",
"mock-fs": "^5.1.0",
"msw": "^0.48.0",
"msw": "^0.49.0",
"nodemon": "^2.0.2",
"ts-node": "^10.0.0",
"type-fest": "^2.0.0"
@@ -41,7 +41,7 @@
"@backstage/cli": "{{versionQuery '@backstage/cli'}}",
"@types/supertest": "{{versionQuery '@types/supertest' '2.0.12'}}",
"supertest": "{{versionQuery 'supertest' '6.2.4'}}",
"msw": "{{versionQuery 'msw' '0.47.0'}}"
"msw": "{{versionQuery 'msw' '0.49.0'}}"
},
"files": [
"dist"
@@ -48,7 +48,7 @@
"@testing-library/react": "{{versionQuery '@testing-library/react' '12.1.3'}}",
"@testing-library/user-event": "{{versionQuery '@testing-library/user-event' '14.0.0'}}",
"@types/node": "{{versionQuery '@types/node' '16.11.26'}}",
"msw": "{{versionQuery 'msw' '0.47.0'}}",
"msw": "{{versionQuery 'msw' '0.49.0'}}",
"cross-fetch": "{{versionQuery 'cross-fetch' '3.1.5'}}"
},
"files": [
+1 -1
View File
@@ -56,7 +56,7 @@
"@types/node": "^16.11.26",
"@types/yup": "^0.29.13",
"mock-fs": "^5.1.0",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -57,7 +57,7 @@
"@types/node": "^16.11.26",
"@types/zen-observable": "^0.8.0",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0",
"msw": "^0.49.0",
"react-router-beta": "npm:react-router@6.0.0-beta.0",
"react-router-dom-beta": "npm:react-router-dom@6.0.0-beta.0",
"react-router-dom-stable": "npm:react-router-dom@^6.3.0",
+1
View File
@@ -250,6 +250,7 @@ export interface DependencyGraphProps<NodeData, EdgeData>
edgeRanks?: number;
edges: DependencyEdge<EdgeData>[];
edgeWeight?: number;
fit?: 'grow' | 'contain';
labelOffset?: number;
// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver
labelPosition?: LabelPosition;
+1 -1
View File
@@ -99,7 +99,7 @@
"@types/react-window": "^1.8.5",
"@types/zen-observable": "^0.8.0",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
@@ -170,6 +170,14 @@ export interface DependencyGraphProps<NodeData, EdgeData>
* Default: 'curveMonotoneX'
*/
curve?: 'curveStepBefore' | 'curveMonotoneX';
/**
* Controls if the graph should be contained or grow
*
* @remarks
*
* Default: 'grow'
*/
fit?: 'grow' | 'contain';
}
const WORKSPACE_ID = 'workspace';
@@ -203,6 +211,7 @@ export function DependencyGraph<NodeData, EdgeData>(
defs,
zoom = 'enabled',
curve = 'curveMonotoneX',
fit = 'grow',
...svgProps
} = props;
const theme: BackstageTheme = useTheme();
@@ -223,6 +232,9 @@ export function DependencyGraph<NodeData, EdgeData>(
const maxWidth = Math.max(graphWidth, containerWidth);
const maxHeight = Math.max(graphHeight, containerHeight);
const minHeight = Math.min(graphHeight, containerHeight);
const scalableHeight = fit === 'grow' ? maxHeight : minHeight;
const containerRef = React.useMemo(
() =>
@@ -394,7 +406,7 @@ export function DependencyGraph<NodeData, EdgeData>(
ref={containerRef}
{...svgProps}
width="100%"
height={maxHeight}
height={scalableHeight}
viewBox={`0 0 ${maxWidth} ${maxHeight}`}
>
<defs>
@@ -15,31 +15,38 @@
*/
import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import {
render,
act,
RenderResult,
waitFor,
fireEvent,
screen,
} from '@testing-library/react';
import { wrapInTestApp, renderInTestApp } from '@backstage/test-utils';
MockConfigApi,
renderInTestApp,
TestApiProvider,
} from '@backstage/test-utils';
import { SupportButton } from './SupportButton';
import { configApiRef } from '@backstage/core-plugin-api';
const configApi = new MockConfigApi({
app: {
support: {
url: 'https://github.com',
items: [
{
title: 'Github',
icon: 'github',
links: [{ title: 'Github Issues', url: '/issues' }],
},
],
},
},
});
const SUPPORT_BUTTON_ID = 'support-button';
const POPOVER_ID = 'support-button-popover';
describe('<SupportButton />', () => {
it('renders without exploding', async () => {
let renderResult: RenderResult;
await renderInTestApp(<SupportButton />);
await act(async () => {
renderResult = render(wrapInTestApp(<SupportButton />));
});
await waitFor(() =>
expect(renderResult.getByTestId(SUPPORT_BUTTON_ID)).toBeInTheDocument(),
);
expect(screen.getByTestId(SUPPORT_BUTTON_ID)).toBeInTheDocument();
});
it('supports passing a title', async () => {
@@ -48,26 +55,52 @@ describe('<SupportButton />', () => {
expect(screen.getByText('Custom title')).toBeInTheDocument();
});
it('supports passing link items through props', async () => {
await renderInTestApp(
<SupportButton
items={[
{
title: 'Documentation',
icon: 'description',
links: [{ title: 'Show docs', url: '/docs' }],
},
]}
/>,
);
const supportButton = screen.getByTestId(SUPPORT_BUTTON_ID);
expect(supportButton).toBeInTheDocument();
fireEvent.click(supportButton);
const documentationItem = screen.getByText('Documentation');
expect(documentationItem).toBeInTheDocument();
});
it('shows items from support config', async () => {
await renderInTestApp(
<TestApiProvider apis={[[configApiRef, configApi]]}>
<SupportButton />
</TestApiProvider>,
);
const supportButton = screen.getByTestId(SUPPORT_BUTTON_ID);
expect(supportButton).toBeInTheDocument();
fireEvent.click(supportButton);
const defaultGithubSupportConfig = screen.getByText('Github Issues');
expect(defaultGithubSupportConfig).toBeInTheDocument();
});
it('shows popover on click', async () => {
let renderResult: RenderResult;
await renderInTestApp(<SupportButton />);
await act(async () => {
renderResult = render(wrapInTestApp(<SupportButton />));
});
const supportButton = screen.getByTestId(SUPPORT_BUTTON_ID);
expect(supportButton).toBeInTheDocument();
let button: HTMLElement;
fireEvent.click(supportButton);
await waitFor(() => {
expect(renderResult.getByTestId(SUPPORT_BUTTON_ID)).toBeInTheDocument();
button = renderResult.getByTestId(SUPPORT_BUTTON_ID);
});
await act(async () => {
fireEvent.click(button);
});
await waitFor(() => {
expect(renderResult.getByTestId(POPOVER_ID)).toBeInTheDocument();
});
expect(screen.getByTestId(POPOVER_ID)).toBeInTheDocument();
});
});
@@ -35,6 +35,7 @@ import { Link } from '../Link';
type SupportButtonProps = {
title?: string;
items?: SupportItem[];
children?: React.ReactNode;
};
@@ -82,8 +83,8 @@ const SupportListItem = ({ item }: { item: SupportItem }) => {
};
export function SupportButton(props: SupportButtonProps) {
const { title, children } = props;
const { items } = useSupportConfig();
const { title, items, children } = props;
const { items: configItems } = useSupportConfig();
const [popoverOpen, setPopoverOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
@@ -150,10 +151,9 @@ export function SupportButton(props: SupportButtonProps) {
{child}
</ListItem>
))}
{items &&
items.map((item, i) => (
<SupportListItem item={item} key={`item-${i}`} />
))}
{(items ?? configItems).map((item, i) => (
<SupportListItem item={item} key={`item-${i}`} />
))}
</List>
<DialogActions>
<Button color="primary" onClick={popoverCloseHandler}>
+1 -1
View File
@@ -57,7 +57,7 @@
"@types/prop-types": "^15.7.3",
"@types/zen-observable": "^0.8.0",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist",
+1 -1
View File
@@ -45,7 +45,7 @@
"@testing-library/user-event": "^14.0.0",
"@types/node": "^16.11.26",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -46,7 +46,7 @@
"@backstage/config-loader": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@types/luxon": "^3.0.0",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist",
+1 -1
View File
@@ -38,7 +38,7 @@
"@backstage/cli": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@types/node": "^16.0.0",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+5
View File
@@ -0,0 +1,5 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'no-console': 0,
},
});
+11
View File
@@ -0,0 +1,11 @@
# @backstage/repo-tools
This package provides a CLI for backstage repo tooling.
## Installation
Install the package via Yarn:
```sh
yarn add @backstage/repo-tools
```
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env node
/*
* Copyright 2022 The Backstage Authors
*
* 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.
*/
const path = require('path');
// Figure out whether we're running inside the backstage repo or as an installed dependency
/* eslint-disable-next-line no-restricted-syntax */
const isLocal = require('fs').existsSync(path.resolve(__dirname, '../src'));
if (!isLocal || process.env.BACKSTAGE_E2E_CLI_TEST) {
require('..');
} else {
require('ts-node').register({
transpileOnly: true,
/* eslint-disable-next-line no-restricted-syntax */
project: path.resolve(__dirname, '../../../tsconfig.json'),
compilerOptions: {
module: 'CommonJS',
},
});
require('../src');
}
+29
View File
@@ -0,0 +1,29 @@
## CLI Report file for "@backstage/repo-tools"
> Do not edit this file. It is a report generated by `yarn build:api-reports`
### `backstage-repo-tools`
```
Usage: backstage-repo-tools [options] [command]
Options:
-V, --version
-h, --help
Commands:
api-reports [options] [path...]
help [command]
```
### `backstage-repo-tools api-reports`
```
Usage: backstage-repo-tools api-reports [options] [path...]
Options:
--ci
--tsc
--docs
-h, --help
```
+52
View File
@@ -0,0 +1,52 @@
{
"name": "@backstage/repo-tools",
"description": "CLI for Backstage repo tooling ",
"version": "0.0.0",
"publishConfig": {
"access": "public"
},
"backstage": {
"role": "cli"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/repo-tools"
},
"keywords": [
"backstage"
],
"license": "Apache-2.0",
"main": "dist/index.cjs.js",
"scripts": {
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"start": "nodemon --"
},
"bin": {
"backstage-repo-tools": "bin/backstage-repo-tools"
},
"dependencies": {
"@backstage/errors": "workspace:^",
"@microsoft/api-documenter": "^7.17.11",
"@microsoft/api-extractor": "^7.23.0",
"@microsoft/api-extractor-model": "^7.17.2",
"@microsoft/tsdoc": "0.14.1",
"chalk": "^4.0.0",
"commander": "^9.1.0",
"fs-extra": "10.1.0",
"ts-node": "^10.0.0"
},
"files": [
"bin",
"dist/**/*.js"
],
"nodemonConfig": {
"watch": "./src",
"exec": "bin/backstage-repo-tools",
"ext": "ts"
}
}
@@ -24,7 +24,7 @@ import {
dirname,
join,
} from 'path';
import { spawnSync, execFile } from 'child_process';
import { execFile } from 'child_process';
import prettier from 'prettier';
import fs from 'fs-extra';
import {
@@ -49,11 +49,17 @@ import {
import { DocTable } from '@microsoft/api-documenter/lib/nodes/DocTable';
import { DocTableRow } from '@microsoft/api-documenter/lib/nodes/DocTableRow';
import { DocHeading } from '@microsoft/api-documenter/lib/nodes/DocHeading';
import { CustomMarkdownEmitter } from '@microsoft/api-documenter/lib/markdown/CustomMarkdownEmitter';
import {
CustomMarkdownEmitter,
ICustomMarkdownEmitterOptions,
} from '@microsoft/api-documenter/lib/markdown/CustomMarkdownEmitter';
import { IMarkdownEmitterContext } from '@microsoft/api-documenter/lib/markdown/MarkdownEmitter';
import { AstDeclaration } from '@microsoft/api-extractor/lib/analyzer/AstDeclaration';
const tmpDir = resolvePath(__dirname, '../node_modules/.cache/api-extractor');
const tmpDir = resolvePath(
process.cwd(),
'./node_modules/.cache/api-extractor',
);
/**
* All of this monkey patching below is because MUI has these bare package.json file as a method
@@ -99,7 +105,9 @@ function patchFileMessageFetcher(
} = router;
router.fetchAssociatedMessagesForReviewFile =
function patchedFetchAssociatedMessagesForReviewFile(ast) {
function patchedFetchAssociatedMessagesForReviewFile(
ast: AstDeclaration | undefined,
) {
const messages = fetchAssociatedMessagesForReviewFile.call(this, ast);
return transform(messages, ast);
};
@@ -113,7 +121,10 @@ function patchFileMessageFetcher(
const originalGenerateReviewFileContent =
ApiReportGenerator.generateReviewFileContent;
ApiReportGenerator.generateReviewFileContent =
function decoratedGenerateReviewFileContent(collector, ...moreArgs) {
function decoratedGenerateReviewFileContent(
collector: { program: Program; messageRouter: any },
...moreArgs: any[]
) {
const program = collector.program as Program;
// The purpose of this override is to allow the @ignore tag to be used to ignore warnings
@@ -137,7 +148,9 @@ ApiReportGenerator.generateReviewFileContent =
}
const [, symbolName] = symbolMatch;
const sourceFile = program.getSourceFile(message.sourceFilePath);
const sourceFile =
message.sourceFilePath &&
program.getSourceFile(message.sourceFilePath);
if (!sourceFile) {
throw new Error(
`Failed to find source file in program at path "${message.sourceFilePath}"`,
@@ -210,7 +223,7 @@ const ALLOW_WARNINGS = [
async function resolvePackagePath(
packagePath: string,
): Promise<string | undefined> {
const projectRoot = resolvePath(__dirname, '..');
const projectRoot = resolvePath(process.cwd());
const fullPackageDir = resolvePath(projectRoot, packagePath);
const stat = await fs.stat(fullPackageDir);
@@ -228,7 +241,7 @@ async function resolvePackagePath(
return relativePath(projectRoot, fullPackageDir);
}
async function findSpecificPackageDirs(unresolvedPackageDirs: string[]) {
export async function findSpecificPackageDirs(unresolvedPackageDirs: string[]) {
const packageDirs = new Array<string>();
for (const unresolvedPackageDir of unresolvedPackageDirs) {
@@ -246,9 +259,9 @@ async function findSpecificPackageDirs(unresolvedPackageDirs: string[]) {
return packageDirs;
}
async function findPackageDirs() {
export async function findPackageDirs() {
const packageDirs = new Array<string>();
const projectRoot = resolvePath(__dirname, '..');
const projectRoot = resolvePath(process.cwd());
for (const packageRoot of PACKAGE_ROOTS) {
const dirs = await fs.readdir(resolvePath(projectRoot, packageRoot));
@@ -265,8 +278,8 @@ async function findPackageDirs() {
return packageDirs;
}
async function createTemporaryTsConfig(includedPackageDirs: string[]) {
const path = resolvePath(__dirname, '..', 'tsconfig.tmp.json');
export async function createTemporaryTsConfig(includedPackageDirs: string[]) {
const path = resolvePath(process.cwd(), 'tsconfig.tmp.json');
process.once('exit', () => {
fs.removeSync(path);
@@ -284,7 +297,7 @@ async function createTemporaryTsConfig(includedPackageDirs: string[]) {
return path;
}
async function countApiReportWarnings(projectFolder: string) {
export async function countApiReportWarnings(projectFolder: string) {
const path = resolvePath(projectFolder, 'api-report.md');
try {
const content = await fs.readFile(path, 'utf8');
@@ -313,7 +326,7 @@ async function countApiReportWarnings(projectFolder: string) {
}
}
async function getTsDocConfig() {
export async function getTsDocConfig() {
const tsdocConfigFile = await TSDocConfigFile.loadFile(
require.resolve('@microsoft/api-extractor/extends/tsdoc-base.json'),
);
@@ -349,7 +362,7 @@ interface ApiExtractionOptions {
tsconfigFilePath: string;
}
async function runApiExtraction({
export async function runApiExtraction({
packageDirs,
outputDir,
isLocalBuild,
@@ -358,7 +371,10 @@ async function runApiExtraction({
await fs.remove(outputDir);
const entryPoints = packageDirs.map(packageDir => {
return resolvePath(__dirname, `../dist-types/${packageDir}/src/index.d.ts`);
return resolvePath(
process.cwd(),
`./dist-types/${packageDir}/src/index.d.ts`,
);
});
let compilerState: CompilerState | undefined = undefined;
@@ -367,8 +383,12 @@ async function runApiExtraction({
for (const packageDir of packageDirs) {
console.log(`## Processing ${packageDir}`);
const projectFolder = resolvePath(__dirname, '..', packageDir);
const packageFolder = resolvePath(__dirname, '../dist-types', packageDir);
const projectFolder = resolvePath(process.cwd(), packageDir);
const packageFolder = resolvePath(
process.cwd(),
'./dist-types',
packageDir,
);
const warningCountBefore = await countApiReportWarnings(projectFolder);
@@ -618,7 +638,7 @@ class ApiModelTransforms {
if (serialized.members.length !== 1) {
throw new Error(
`Unexpected members in serialized ApiPackage, [${serialized.members
.map(m => m.kind)
.map((m: { kind: any }) => m.kind)
.join(' ')}]`,
);
}
@@ -634,7 +654,7 @@ class ApiModelTransforms {
members: [
{
...entryPoint,
members: entryPoint.members.map(member =>
members: entryPoint.members.map((member: any) =>
transforms.reduce((m, t) => t(m), member),
),
},
@@ -753,7 +773,7 @@ class ApiModelTransforms {
};
}
async function buildDocs({
export async function buildDocs({
inputDir,
outputDir,
}: {
@@ -847,7 +867,11 @@ async function buildDocs({
}
/** @override */
emit(stringBuilder, docNode, options) {
emit(
stringBuilder: any,
docNode: DocNode,
options: ICustomMarkdownEmitterOptions,
) {
// Hack to get rid of the leading comment of each file, since
// we want the front matter to come first
stringBuilder._chunks.length = 0;
@@ -886,7 +910,7 @@ async function buildDocs({
// so we hook in wherever we can. In this case we add the front matter
// just before writing the breadcrumbs at the top.
/** @override */
_writeBreadcrumb(output, apiItem) {
_writeBreadcrumb(output: any, apiItem: ApiItem & { name: string }) {
let title;
let description;
@@ -897,9 +921,12 @@ async function buildDocs({
} else if (apiItem.kind === 'Model') {
title = 'Package Index';
description = 'Index of all Backstage Packages';
} else {
} else if (apiItem.name) {
title = apiItem.name;
description = `API Reference for ${apiItem.name}`;
} else {
title = apiItem.displayName;
description = `API Reference for ${apiItem.displayName}`;
}
// Add our front matter
@@ -925,7 +952,10 @@ async function buildDocs({
};
}
_writeModelTable(output, apiModel): void {
_writeModelTable(
output: { appendNode: (arg0: DocTable | DocHeading) => void },
apiModel: { members: any },
): void {
const configuration = this._tsdocConfiguration;
const packagesTable = new DocTable({
@@ -998,7 +1028,10 @@ async function buildDocs({
documenter.generateFiles();
}
async function categorizePackageDirs(projectRoot, packageDirs) {
export async function categorizePackageDirs(
projectRoot: string,
packageDirs: any[],
) {
const dirs = packageDirs.slice();
const tsPackageDirs = new Array<string>();
const cliPackageDirs = new Array<string>();
@@ -1086,11 +1119,11 @@ function parseHelpPage(helpPageContent: string) {
// Trim away documentation
const sectionItems = sectionLines
.map(line => line.match(/^\s{1,8}(.*?)\s\s+/)?.[1])
.filter(Boolean);
.filter(Boolean) as string[];
if (sectionName.toLocaleLowerCase('en-US') === 'options:') {
if (sectionName?.toLocaleLowerCase('en-US') === 'options:') {
options = sectionItems;
} else if (sectionName.toLocaleLowerCase('en-US') === 'commands:') {
} else if (sectionName?.toLocaleLowerCase('en-US') === 'commands:') {
commands = sectionItems;
} else {
throw new Error(`Unknown CLI section: ${sectionName}`);
@@ -1185,7 +1218,7 @@ interface CliExtractionOptions {
isLocalBuild: boolean;
}
async function runCliExtraction({
export async function runCliExtraction({
projectRoot,
packageDirs,
isLocalBuild,
@@ -1250,96 +1283,3 @@ async function runCliExtraction({
}
}
}
async function main() {
const projectRoot = resolvePath(__dirname, '..');
const isCiBuild = process.argv.includes('--ci');
const isDocsBuild = process.argv.includes('--docs');
const runTsc = process.argv.includes('--tsc');
const selectedPackageDirs = await findSpecificPackageDirs(
process.argv.slice(2).filter(arg => !arg.startsWith('--')),
);
if (selectedPackageDirs && isCiBuild) {
throw new Error(
'Package path arguments are not supported together with the --ci flag',
);
}
if (!selectedPackageDirs && !isCiBuild && !isDocsBuild) {
console.log('');
console.log(
'TIP: You can generate api-reports for select packages by passing package paths:',
);
console.log('');
console.log(
' yarn build:api-reports packages/config packages/core-plugin-api',
);
console.log('');
}
let temporaryTsConfigPath: string | undefined;
if (selectedPackageDirs) {
temporaryTsConfigPath = await createTemporaryTsConfig(selectedPackageDirs);
}
const tsconfigFilePath =
temporaryTsConfigPath ?? resolvePath(projectRoot, 'tsconfig.json');
if (runTsc) {
await fs.remove(resolvePath(projectRoot, 'dist-types'));
const { status } = spawnSync(
'yarn',
[
'tsc',
['--project', tsconfigFilePath],
['--skipLibCheck', 'false'],
['--incremental', 'false'],
].flat(),
{
stdio: 'inherit',
shell: true,
cwd: projectRoot,
},
);
if (status !== 0) {
process.exit(status);
}
}
const packageDirs = selectedPackageDirs ?? (await findPackageDirs());
const { tsPackageDirs, cliPackageDirs } = await categorizePackageDirs(
projectRoot,
packageDirs,
);
if (tsPackageDirs.length > 0) {
console.log('# Generating package API reports');
await runApiExtraction({
packageDirs: tsPackageDirs,
outputDir: tmpDir,
isLocalBuild: !isCiBuild,
tsconfigFilePath,
});
}
if (cliPackageDirs.length > 0) {
console.log('# Generating package CLI reports');
await runCliExtraction({
projectRoot,
packageDirs: cliPackageDirs,
isLocalBuild: !isCiBuild,
});
}
if (isDocsBuild) {
console.log('# Generating package documentation');
await buildDocs({
inputDir: tmpDir,
outputDir: resolvePath(projectRoot, 'docs/reference'),
});
}
}
main().catch(error => {
console.error(error.stack || String(error));
process.exit(1);
});
@@ -0,0 +1,120 @@
/*
* Copyright 2022 The Backstage Authors
*
* 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 { OptionValues } from 'commander';
import { resolve as resolvePath } from 'path';
import fs from 'fs-extra';
import { spawnSync } from 'child_process';
import {
findSpecificPackageDirs,
createTemporaryTsConfig,
findPackageDirs,
categorizePackageDirs,
runApiExtraction,
runCliExtraction,
buildDocs,
} from './api-extractor';
export default async (paths: string[], opts: OptionValues) => {
const tmpDir = resolvePath(
process.cwd(),
'./node_modules/.cache/api-extractor',
);
const projectRoot = resolvePath(process.cwd());
const isCiBuild = opts.ci;
const isDocsBuild = opts.docs;
const runTsc = opts.tsc;
const selectedPackageDirs = await findSpecificPackageDirs(paths);
if (selectedPackageDirs && isCiBuild) {
throw new Error(
'Package path arguments are not supported together with the --ci flag',
);
}
if (!selectedPackageDirs && !isCiBuild && !isDocsBuild) {
console.log('');
console.log(
'TIP: You can generate api-reports for select packages by passing package paths:',
);
console.log('');
console.log(
' yarn build:api-reports packages/config packages/core-plugin-api',
);
console.log('');
}
let temporaryTsConfigPath: string | undefined;
if (selectedPackageDirs) {
temporaryTsConfigPath = await createTemporaryTsConfig(selectedPackageDirs);
}
const tsconfigFilePath =
temporaryTsConfigPath ?? resolvePath(projectRoot, 'tsconfig.json');
if (runTsc) {
await fs.remove(resolvePath(projectRoot, 'dist-types'));
const { status } = spawnSync(
'yarn',
[
'tsc',
['--project', tsconfigFilePath],
['--skipLibCheck', 'false'],
['--incremental', 'false'],
].flat(),
{
stdio: 'inherit',
shell: true,
cwd: projectRoot,
},
);
if (status !== 0) {
process.exit(status || undefined);
}
}
const packageDirs = selectedPackageDirs ?? (await findPackageDirs());
const { tsPackageDirs, cliPackageDirs } = await categorizePackageDirs(
projectRoot,
packageDirs,
);
if (tsPackageDirs.length > 0) {
console.log('# Generating package API reports');
await runApiExtraction({
packageDirs: tsPackageDirs,
outputDir: tmpDir,
isLocalBuild: !isCiBuild,
tsconfigFilePath,
});
}
if (cliPackageDirs.length > 0) {
console.log('# Generating package CLI reports');
await runCliExtraction({
projectRoot,
packageDirs: cliPackageDirs,
isLocalBuild: !isCiBuild,
});
}
if (isDocsBuild) {
console.log('# Generating package documentation');
await buildDocs({
inputDir: tmpDir,
outputDir: resolvePath(projectRoot, 'docs/reference'),
});
}
};
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright 2022 The Backstage Authors
*
* 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 { assertError } from '@backstage/errors';
import { Command } from 'commander';
import { exitWithError } from '../lib/errors';
export function registerCommands(program: Command) {
program
.command('api-reports [path...]')
.option('--ci', 'CI run checks that there is no changes on API reports')
.option('--tsc', 'executes the tsc compilation before extracting the APIs')
.option('--docs', 'generates the api documentation')
.description('Generate an API report for selected packages')
.action(
lazy(() => import('./api-reports/api-reports').then(m => m.default)),
);
}
// Wraps an action function so that it always exits and handles errors
function lazy(
getActionFunc: () => Promise<(...args: any[]) => Promise<void>>,
): (...args: any[]) => Promise<never> {
return async (...args: any[]) => {
try {
const actionFunc = await getActionFunc();
await actionFunc(...args);
process.exit(0);
} catch (error) {
assertError(error);
exitWithError(error);
}
};
}
+52
View File
@@ -0,0 +1,52 @@
/*
* Copyright 2020 The Backstage Authors
*
* 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.
*/
/**
* CLI for Backstage repo tooling
*
* @packageDocumentation
*/
import { program } from 'commander';
import chalk from 'chalk';
import { exitWithError } from './lib/errors';
import { registerCommands } from './commands';
const main = (argv: string[]) => {
program.name('backstage-repo-tools').version('1.0');
registerCommands(program);
program.on('command:*', () => {
console.log();
console.log(chalk.red(`Invalid command: ${program.args.join(' ')}`));
console.log();
program.outputHelp();
process.exit(1);
});
program.parse(argv);
};
process.on('unhandledRejection', rejection => {
if (rejection instanceof Error) {
exitWithError(rejection);
} else {
exitWithError(new Error(`Unknown rejection: '${rejection}'`));
}
});
main(process.argv);
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright 2020 The Backstage Authors
*
* 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 chalk from 'chalk';
export class CustomError extends Error {
get name(): string {
return this.constructor.name;
}
}
export class ExitCodeError extends CustomError {
readonly code: number;
constructor(code: number, command?: string) {
super(
command
? `Command '${command}' exited with code ${code}`
: `Child exited with code ${code}`,
);
this.code = code;
}
}
export function exitWithError(error: Error): never {
if (error instanceof ExitCodeError) {
process.stderr.write(`\n${chalk.red(error.message)}\n\n`);
process.exit(error.code);
} else {
process.stderr.write(`\n${chalk.red(`${error}`)}\n\n`);
process.exit(1);
}
}
export class NotFoundError extends CustomError {}
+1 -1
View File
@@ -57,7 +57,7 @@
"devDependencies": {
"@backstage/cli": "workspace:^",
"@types/node": "^16.11.26",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -46,7 +46,7 @@
"@backstage/cli": "workspace:^",
"@types/marked": "^4.0.0",
"@types/supertest": "^2.0.8",
"msw": "^0.48.0",
"msw": "^0.49.0",
"supertest": "^6.1.3"
},
"files": [
+1 -1
View File
@@ -55,7 +55,7 @@
"@types/git-url-parse": "^9.0.0",
"@types/node": "*",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -35,7 +35,7 @@
"@backstage/cli": "workspace:^",
"@types/http-proxy-middleware": "^0.19.3",
"@types/supertest": "^2.0.8",
"msw": "^0.48.0",
"msw": "^0.49.0",
"supertest": "^6.1.6"
},
"files": [
+1 -1
View File
@@ -50,7 +50,7 @@
"@types/node": "^16.11.26",
"@types/object-hash": "^2.2.1",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -47,7 +47,7 @@
"@testing-library/user-event": "^14.0.0",
"@types/node": "^16.11.26",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -45,7 +45,7 @@
"@testing-library/user-event": "^14.0.0",
"@types/node": "^16.11.26",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist",
+1 -1
View File
@@ -43,7 +43,7 @@
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^14.0.0",
"@types/node": "^16.11.26",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist",
+1 -1
View File
@@ -66,7 +66,7 @@
"@types/node": "^16.11.26",
"@types/swagger-ui-react": "^4.1.1",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -45,7 +45,7 @@
"@testing-library/user-event": "^14.0.0",
"@types/node": "*",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -58,7 +58,7 @@
"@types/supertest": "^2.0.8",
"get-port": "^6.1.2",
"mock-fs": "^5.1.0",
"msw": "^0.48.0",
"msw": "^0.49.0",
"node-fetch": "^2.6.7",
"supertest": "^6.1.3"
},
+1 -1
View File
@@ -89,7 +89,7 @@
"@types/passport-saml": "^1.1.3",
"@types/passport-strategy": "^0.2.35",
"@types/xml2js": "^0.4.7",
"msw": "^0.48.0",
"msw": "^0.49.0",
"supertest": "^6.1.3"
},
"files": [
+1 -1
View File
@@ -35,7 +35,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"lodash": "^4.17.21",
"msw": "^0.48.0",
"msw": "^0.49.0",
"uuid": "^8.0.0"
},
"files": [
+1 -1
View File
@@ -37,7 +37,7 @@
"devDependencies": {
"@backstage/cli": "workspace:^",
"@types/supertest": "^2.0.8",
"msw": "^0.48.0",
"msw": "^0.49.0",
"supertest": "^6.1.6"
},
"files": [
+1 -1
View File
@@ -56,7 +56,7 @@
"@testing-library/user-event": "^14.0.0",
"@types/node": "^16.11.26",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -48,7 +48,7 @@
"devDependencies": {
"@backstage/cli": "workspace:^",
"@types/supertest": "^2.0.8",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -57,7 +57,7 @@
"@testing-library/user-event": "^14.0.0",
"@types/node": "^16.11.26",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -54,7 +54,7 @@
"@testing-library/user-event": "^14.0.0",
"@types/node": "^16.11.26",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -33,7 +33,7 @@
"devDependencies": {
"@backstage/cli": "workspace:^",
"@openapitools/openapi-generator-cli": "^2.4.26",
"msw": "^0.48.0",
"msw": "^0.49.0",
"ts-morph": "^15.0.0"
},
"files": [
+1 -1
View File
@@ -51,7 +51,7 @@
"@testing-library/user-event": "^14.0.0",
"@types/node": "^16.11.26",
"@types/recharts": "^1.8.15",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
@@ -53,7 +53,7 @@
"@backstage/cli": "workspace:^",
"@types/lodash": "^4.14.151",
"luxon": "^3.0.0",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"alpha",
@@ -54,7 +54,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"luxon": "^3.0.0",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"alpha",
@@ -50,7 +50,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"luxon": "^3.0.0",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"alpha",
@@ -49,7 +49,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@types/lodash": "^4.14.151",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
@@ -48,7 +48,7 @@
"@backstage/cli": "workspace:^",
"@types/fs-extra": "^9.0.1",
"luxon": "^3.0.0",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist",
@@ -57,7 +57,7 @@
"@backstage/cli": "workspace:^",
"@types/lodash": "^4.14.151",
"luxon": "^3.0.0",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist",
@@ -79,7 +79,7 @@ describe('assignGroupsToUsers', () => {
spec: {
type: 'team',
children: [],
members: ['u1', 'u2'],
members: ['default/u1', 'default/u2'],
},
},
{
@@ -17,6 +17,8 @@
import {
DEFAULT_NAMESPACE,
GroupEntity,
parseEntityRef,
stringifyEntityRef,
UserEntity,
} from '@backstage/catalog-model';
@@ -65,14 +67,20 @@ export function assignGroupsToUsers(
group.metadata.namespace !== DEFAULT_NAMESPACE
? `${group.metadata.namespace}/${group.metadata.name}`
: group.metadata.name;
return [groupKey, group.spec.members || []];
// Fully qualify member refs so they can be keyed off of since they may contain namespace prefixes
return [
groupKey,
group.spec.members?.map(m =>
stringifyEntityRef(parseEntityRef(m, { defaultKind: 'user' })),
) || [],
];
}),
);
const usersByName = new Map(users.map(u => [u.metadata.name, u]));
for (const [groupName, userNames] of groupMemberUsers.entries()) {
for (const userName of userNames) {
const user = usersByName.get(userName);
const usersByRef = new Map(users.map(u => [stringifyEntityRef(u), u]));
for (const [groupName, userRefs] of groupMemberUsers.entries()) {
for (const ref of userRefs) {
const user = usersByRef.get(ref);
if (user && !user.spec.memberOf?.includes(groupName)) {
if (!user.spec.memberOf) {
user.spec.memberOf = [];
@@ -14,7 +14,12 @@
* limitations under the License.
*/
import { GroupEntity } from '@backstage/catalog-model';
import {
DEFAULT_NAMESPACE,
GroupEntity,
stringifyEntityRef,
UserEntity,
} from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import {
DefaultGithubCredentialsProvider,
@@ -36,6 +41,7 @@ import {
assignGroupsToUsers,
buildOrgHierarchy,
defaultOrganizationTeamTransformer,
defaultUserTransformer,
getOrganizationTeams,
getOrganizationUsers,
GithubMultiOrgConfig,
@@ -141,8 +147,19 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor {
client,
orgConfig.name,
tokenType,
this.options.userTransformer,
async (githubUser, ctx): Promise<UserEntity | undefined> => {
const result = this.options.userTransformer
? await this.options.userTransformer(githubUser, ctx)
: await defaultUserTransformer(githubUser, ctx);
if (result) {
result.metadata.namespace = orgConfig.userNamespace;
}
return result;
},
);
const { groups } = await getOrganizationTeams(
client,
orgConfig.name,
@@ -153,6 +170,13 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor {
if (result) {
result.metadata.namespace = orgConfig.groupNamespace;
// Group `spec.members` inherits the namespace of it's group so need to explicitly specify refs here
result.spec.members = team.members.map(
user =>
`${orgConfig.userNamespace ?? DEFAULT_NAMESPACE}/${
user.login
}`,
);
}
return result;
@@ -164,15 +188,18 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor {
`Read ${users.length} GitHub users and ${groups.length} GitHub teams from ${orgConfig.name} in ${duration} seconds`,
);
let prefix: string = orgConfig.userNamespace ?? '';
if (prefix.length > 0) prefix += '/';
users.forEach(u => {
if (!allUsersMap.has(prefix + u.metadata.name)) {
allUsersMap.set(prefix + u.metadata.name, u);
// Grab current users from `allUsersMap` if they already exist in our
// pending users so we can append to their group membership relations
const pendingUsers = users.map(u => {
const userRef = stringifyEntityRef(u);
if (!allUsersMap.has(userRef)) {
allUsersMap.set(userRef, u);
}
return allUsersMap.get(userRef);
});
assignGroupsToUsers(users, groups);
assignGroupsToUsers(pendingUsers, groups);
buildOrgHierarchy(groups);
for (const group of groups) {
@@ -185,6 +212,8 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor {
}
}
// Emit all users at the end after all orgs have been processed
// so all memberships across org groups are accounted for
const allUsers = Array.from(allUsersMap.values());
for (const user of allUsers) {
emit(processingResult.entity(location, user));
@@ -85,7 +85,10 @@ describe('GithubOrgReaderProcessor', () => {
mockClient
.mockResolvedValueOnce({
organization: {
membersWithRole: { pageInfo: { hasNextPage: false }, nodes: [{}] },
membersWithRole: {
pageInfo: { hasNextPage: false },
nodes: [{ login: 'foo' }],
},
},
})
.mockResolvedValueOnce({
@@ -93,7 +96,12 @@ describe('GithubOrgReaderProcessor', () => {
teams: {
pageInfo: { hasNextPage: false },
nodes: [
{ members: { pageInfo: { hasNextPage: false }, nodes: [{}] } },
{
members: {
pageInfo: { hasNextPage: false },
nodes: [{ login: 'foo' }],
},
},
],
},
},
@@ -134,7 +142,10 @@ describe('GithubOrgReaderProcessor', () => {
mockClient
.mockResolvedValueOnce({
organization: {
membersWithRole: { pageInfo: { hasNextPage: false }, nodes: [{}] },
membersWithRole: {
pageInfo: { hasNextPage: false },
nodes: [{ login: 'foo' }],
},
},
})
.mockResolvedValueOnce({
@@ -142,7 +153,12 @@ describe('GithubOrgReaderProcessor', () => {
teams: {
pageInfo: { hasNextPage: false },
nodes: [
{ members: { pageInfo: { hasNextPage: false }, nodes: [{}] } },
{
members: {
pageInfo: { hasNextPage: false },
nodes: [{ login: 'foo' }],
},
},
],
},
},
@@ -54,7 +54,7 @@
"@types/lodash": "^4.14.151",
"@types/uuid": "^8.0.0",
"luxon": "^3.0.0",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"alpha",
@@ -55,7 +55,7 @@
"@backstage/cli": "workspace:^",
"@types/lodash": "^4.14.151",
"luxon": "^3.0.0",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"alpha",
+1 -1
View File
@@ -80,7 +80,7 @@
"@types/uuid": "^8.0.0",
"better-sqlite3": "^7.5.0",
"luxon": "^3.0.0",
"msw": "^0.48.0",
"msw": "^0.49.0",
"supertest": "^6.1.3",
"wait-for-expect": "^3.0.2"
},
+1 -1
View File
@@ -53,7 +53,7 @@
"@graphql-codegen/typescript": "^2.4.2",
"@graphql-codegen/typescript-resolvers": "^2.4.3",
"@graphql-tools/schema": "^9.0.0",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -68,7 +68,7 @@
"@testing-library/react-hooks": "^8.0.0",
"@testing-library/user-event": "^14.0.0",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist",
+1 -1
View File
@@ -63,7 +63,7 @@
"@types/humanize-duration": "^3.25.1",
"@types/node": "^16.11.26",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -60,7 +60,7 @@
"@testing-library/user-event": "^14.0.0",
"@types/node": "^16.11.26",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -49,7 +49,7 @@
"@types/luxon": "^3.0.0",
"@types/node": "^16.11.26",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -42,7 +42,7 @@
"@backstage/cli": "workspace:^",
"@types/express-xml-bodyparser": "^0.3.2",
"@types/supertest": "^2.0.8",
"msw": "^0.48.0",
"msw": "^0.49.0",
"supertest": "^6.1.6",
"xml2js": "^0.4.23"
},
+1 -1
View File
@@ -56,7 +56,7 @@
"@types/node": "^16.11.26",
"@types/recharts": "^1.8.15",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+1 -1
View File
@@ -47,7 +47,7 @@
"@testing-library/user-event": "^14.0.0",
"@types/node": "*",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist",
+1 -1
View File
@@ -49,7 +49,7 @@
"@testing-library/user-event": "^14.0.0",
"@types/node": "^16.11.26",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist"
+15
View File
@@ -178,6 +178,21 @@ costInsights:
name: Metric C
```
### Base Currency (Optional)
In the case you would like to show your baseline costs on the graph on other currency than US dollars.
```yaml
## ./app-config.yaml
costInsights:
engineerCost: 200000
baseCurrency:
locale: nl-NL
options:
currency: EUR
minimumFractionDigits: 3
```
### Currencies (Optional)
In the `Cost Overview` panel, users can choose from a dropdown of currencies to see costs in, such as Engineers or USD. Currencies must be defined as keys on the `currencies` field. A user-friendly label and unit are **required**. If not set, the `defaultCurrencies` in `currency.ts` will be used.
+1
View File
@@ -227,6 +227,7 @@ export type ChartData = {
// @public (undocumented)
export type ConfigContextProps = {
baseCurrency: Intl.NumberFormat;
metrics: Metric[];
products: Product[];
icons: Icon[];
+52
View File
@@ -21,6 +21,58 @@ export interface Config {
*/
engineerCost: number;
/**
* @visibility frontend
*/
baseCurrency?: {
/**
* @visibility frontend
*/
locale?: string;
options?: {
/**
* @visibility frontend
*/
localeMatcher?: string | undefined;
/**
* @visibility frontend
*/
style?: string | undefined;
/**
* @visibility frontend
*/
currency?: string | undefined;
/**
* @visibility frontend
*/
currencySign?: string | undefined;
/**
* @visibility frontend
*/
useGrouping?: boolean | undefined;
/**
* @visibility frontend
*/
minimumIntegerDigits?: number | undefined;
/**
* @visibility frontend
*/
minimumFractionDigits?: number | undefined;
/**
* @visibility frontend
*/
maximumFractionDigits?: number | undefined;
/**
* @visibility frontend
*/
minimumSignificantDigits?: number | undefined;
/**
* @visibility frontend
*/
maximumSignificantDigits?: number | undefined;
};
};
products?: {
[kind: string]: {
/**
+1 -1
View File
@@ -74,7 +74,7 @@
"@types/yup": "^0.29.13",
"canvas": "^2.10.2",
"cross-fetch": "^3.1.5",
"msw": "^0.48.0"
"msw": "^0.49.0"
},
"files": [
"dist",
@@ -18,7 +18,7 @@ import React from 'react';
import { fireEvent } from '@testing-library/react';
import { BarChart, BarChartProps } from './BarChart';
import { ResourceData } from '../../types';
import { createMockEntity } from '../../testUtils';
import { createMockEntity, MockConfigProvider } from '../../testUtils';
import { resourceSort } from '../../utils/sort';
import { renderInTestApp } from '@backstage/test-utils';
@@ -46,11 +46,13 @@ const renderWithProps = ({
resources = MockResources,
}: BarChartProps) => {
return renderInTestApp(
<BarChart
responsive={responsive}
displayAmount={displayAmount}
resources={resources}
/>,
<MockConfigProvider>
<BarChart
responsive={responsive}
displayAmount={displayAmount}
resources={resources}
/>
</MockConfigProvider>,
);
};
@@ -40,20 +40,24 @@ import { notEmpty } from '../../utils/assert';
import { useBarChartStyles } from '../../utils/styles';
import { resourceSort } from '../../utils/sort';
import { isInvalid, titleOf, tooltipItemOf } from '../../utils/graphs';
import { TooltipRenderer } from '../../types/Tooltip';
import { TooltipRenderer } from '../../types';
import { useConfig } from '../../hooks';
export const defaultTooltip: TooltipRenderer = ({ label, payload = [] }) => {
if (isInvalid({ label, payload })) return null;
const defaultTooltip = (baseCurrency: Intl.NumberFormat) => {
const tooltip: TooltipRenderer = ({ label, payload = [] }) => {
if (isInvalid({ label, payload })) return null;
const title = titleOf(label);
const items = payload.map(tooltipItemOf).filter(notEmpty);
return (
<BarChartTooltip title={title}>
{items.map((item, index) => (
<BarChartTooltipItem key={`${item.label}-${index}`} item={item} />
))}
</BarChartTooltip>
);
const title = titleOf(label);
const items = payload.map(tooltipItemOf(baseCurrency)).filter(notEmpty);
return (
<BarChartTooltip title={title}>
{items.map((item, index) => (
<BarChartTooltipItem key={`${item.label}-${index}`} item={item} />
))}
</BarChartTooltip>
);
};
return tooltip;
};
/** @public */
@@ -69,12 +73,14 @@ export type BarChartProps = {
/** @public */
export const BarChart = (props: BarChartProps) => {
const { baseCurrency } = useConfig();
const {
resources,
responsive = true,
displayAmount = 6,
options = {},
tooltip = defaultTooltip,
tooltip = defaultTooltip(baseCurrency),
onClick,
onMouseMove,
} = props;
@@ -164,7 +170,7 @@ export const BarChart = (props: BarChartProps) => {
tick={BarChartTick}
/>
<YAxis
tickFormatter={currencyFormatter.format}
tickFormatter={currencyFormatter(baseCurrency).format}
domain={[() => 0, globalResourcesMax]}
tick={styles.axis}
/>
@@ -17,11 +17,14 @@
import React from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import { BarChartLegend } from './BarChartLegend';
import { MockConfigProvider } from '../../testUtils';
describe('<BarChartLegend />', () => {
it(`Should display the correct cost start and end`, async () => {
const rendered = await renderInTestApp(
<BarChartLegend costStart={1000} costEnd={5000} />,
<MockConfigProvider>
<BarChartLegend costStart={1000} costEnd={5000} />,
</MockConfigProvider>,
);
expect(rendered.getByText(/\$1,000/)).toBeInTheDocument();
expect(rendered.queryByText(/\$5,000/)).toBeInTheDocument();
@@ -20,6 +20,7 @@ import { LegendItem } from '../LegendItem';
import { currencyFormatter } from '../../utils/formatters';
import { CostInsightsTheme } from '../../types';
import { useBarChartLayoutStyles as useStyles } from '../../utils/styles';
import { useConfig } from '../../hooks';
/** @public */
export type BarChartLegendOptions = {
@@ -45,6 +46,7 @@ export const BarChartLegend = (
const theme = useTheme<CostInsightsTheme>();
const classes = useStyles();
const { baseCurrency } = useConfig();
const data = Object.assign(
{
@@ -63,7 +65,7 @@ export const BarChartLegend = (
title={data.previousName}
markerColor={options.hideMarker ? undefined : data.previousFill}
>
{currencyFormatter.format(costStart)}
{currencyFormatter(baseCurrency).format(costStart)}
</LegendItem>
</Box>
<Box marginRight={2}>
@@ -71,7 +73,7 @@ export const BarChartLegend = (
title={data.currentName}
markerColor={options.hideMarker ? undefined : data.currentFill}
>
{currencyFormatter.format(costEnd)}
{currencyFormatter(baseCurrency).format(costEnd)}
</LegendItem>
</Box>
{children}

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