Merge remote-tracking branch 'origin/master' into cost-insight-trending-line
# Conflicts: # plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx # plugins/cost-insights/src/testUtils/providers.tsx
This commit is contained in:
@@ -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} />
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/repo-tools': minor
|
||||
---
|
||||
|
||||
Introducing repo-tools package
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Initialize all `formData` in the `Stepper` in `/next`
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Bump `msw` version in default plugin/app templates
|
||||
@@ -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.
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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`.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
+3
-6
@@ -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",
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
"msw": "^0.48.0"
|
||||
"msw": "^0.49.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
|
||||
rules: {
|
||||
'no-console': 0,
|
||||
},
|
||||
});
|
||||
@@ -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
|
||||
```
|
||||
Executable
+37
@@ -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');
|
||||
}
|
||||
@@ -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
|
||||
```
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
+63
-123
@@ -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'),
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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 {}
|
||||
@@ -58,7 +58,7 @@
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@types/node": "^16.11.26",
|
||||
"msw": "^0.48.0"
|
||||
"msw": "^0.49.0"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"msw": "^0.48.0"
|
||||
"msw": "^0.49.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
+38
-9
@@ -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));
|
||||
|
||||
+20
-4
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -227,6 +227,7 @@ export type ChartData = {
|
||||
|
||||
// @public (undocumented)
|
||||
export type ConfigContextProps = {
|
||||
baseCurrency: Intl.NumberFormat;
|
||||
metrics: Metric[];
|
||||
products: Product[];
|
||||
icons: Icon[];
|
||||
|
||||
Vendored
+52
@@ -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]: {
|
||||
/**
|
||||
|
||||
@@ -75,7 +75,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}
|
||||
|
||||
+16
-14
@@ -16,41 +16,41 @@
|
||||
import React, { useState } from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
import {
|
||||
useTheme,
|
||||
Box,
|
||||
Typography,
|
||||
Divider,
|
||||
emphasize,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@material-ui/core';
|
||||
import { default as FullScreenIcon } from '@material-ui/icons/Fullscreen';
|
||||
import {
|
||||
Area,
|
||||
AreaChart,
|
||||
CartesianGrid,
|
||||
ResponsiveContainer,
|
||||
Tooltip as RechartsTooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Tooltip as RechartsTooltip,
|
||||
Area,
|
||||
ResponsiveContainer,
|
||||
CartesianGrid,
|
||||
} from 'recharts';
|
||||
import { Cost, DEFAULT_DATE_FORMAT, CostInsightsTheme } from '../../types';
|
||||
import { Cost, CostInsightsTheme, DEFAULT_DATE_FORMAT } from '../../types';
|
||||
import {
|
||||
BarChartLegend,
|
||||
BarChartTooltip as Tooltip,
|
||||
BarChartTooltipItem as TooltipItem,
|
||||
BarChartLegend,
|
||||
} from '../BarChart';
|
||||
import {
|
||||
overviewGraphTickFormatter,
|
||||
formatGraphValue,
|
||||
isInvalid,
|
||||
overviewGraphTickFormatter,
|
||||
} from '../../utils/graphs';
|
||||
import { useCostOverviewStyles as useStyles } from '../../utils/styles';
|
||||
import { useFilters, useLastCompleteBillingDate } from '../../hooks';
|
||||
import { useConfig, useFilters, useLastCompleteBillingDate } from '../../hooks';
|
||||
import { mapFiltersToProps } from './selector';
|
||||
import { getPreviousPeriodTotalCost } from '../../utils/change';
|
||||
import { formatPeriod } from '../../utils/formatters';
|
||||
import { aggregationSum } from '../../utils/sum';
|
||||
import { BarChartLegendOptions } from '../BarChart/BarChartLegend';
|
||||
import { TooltipRenderer } from '../../types/Tooltip';
|
||||
import { BarChartLegendOptions } from '../BarChart';
|
||||
import { TooltipRenderer } from '../../types';
|
||||
|
||||
export type CostOverviewBreakdownChartProps = {
|
||||
costBreakdown: Cost[];
|
||||
@@ -65,6 +65,7 @@ export const CostOverviewBreakdownChart = ({
|
||||
}: CostOverviewBreakdownChartProps) => {
|
||||
const theme = useTheme<CostInsightsTheme>();
|
||||
const classes = useStyles(theme);
|
||||
const { baseCurrency } = useConfig();
|
||||
const lastCompleteBillingDate = useLastCompleteBillingDate();
|
||||
const { duration } = useFilters(mapFiltersToProps);
|
||||
const [isExpanded, setExpanded] = useState(false);
|
||||
@@ -185,9 +186,10 @@ export const CostOverviewBreakdownChart = ({
|
||||
? DateTime.fromMillis(label)
|
||||
: DateTime.fromISO(label!);
|
||||
const dateTitle = date.toUTC().toFormat(DEFAULT_DATE_FORMAT);
|
||||
const formatGraphValueWith = formatGraphValue(baseCurrency);
|
||||
const items = payload.map((p, i) => ({
|
||||
label: p.dataKey as string,
|
||||
value: formatGraphValue(Number(p.value), i),
|
||||
value: formatGraphValueWith(Number(p.value), i),
|
||||
fill: p.color!,
|
||||
}));
|
||||
const expandText = (
|
||||
@@ -253,7 +255,7 @@ export const CostOverviewBreakdownChart = ({
|
||||
<YAxis
|
||||
domain={[() => 0, 'dataMax']}
|
||||
tick={{ fill: classes.axis.fill }}
|
||||
tickFormatter={formatGraphValue}
|
||||
tickFormatter={formatGraphValue(baseCurrency)}
|
||||
width={classes.yAxis.width}
|
||||
/>
|
||||
{renderAreas()}
|
||||
|
||||
@@ -50,6 +50,7 @@ import { aggregationSort } from '../../utils/sort';
|
||||
import { CostOverviewLegend } from './CostOverviewLegend';
|
||||
import { TooltipRenderer } from '../../types';
|
||||
import { useCostInsightsOptions } from '../../options';
|
||||
import { useConfig } from '../../hooks';
|
||||
|
||||
type CostOverviewChartProps = {
|
||||
metric: Maybe<Metric>;
|
||||
@@ -66,6 +67,7 @@ export const CostOverviewChart = ({
|
||||
}: CostOverviewChartProps) => {
|
||||
const theme = useTheme<CostInsightsTheme>();
|
||||
const styles = useStyles(theme);
|
||||
const { baseCurrency } = useConfig();
|
||||
|
||||
const data = {
|
||||
dailyCost: {
|
||||
@@ -107,6 +109,7 @@ export const CostOverviewChart = ({
|
||||
? DateTime.fromMillis(label)
|
||||
: DateTime.fromISO(label!);
|
||||
const title = date.toUTC().toFormat(DEFAULT_DATE_FORMAT);
|
||||
const formatGraphValueWith = formatGraphValue(baseCurrency);
|
||||
const items = payload
|
||||
.filter(p => dataKeys.includes(p.dataKey as string))
|
||||
.map((p, i) => ({
|
||||
@@ -116,8 +119,8 @@ export const CostOverviewChart = ({
|
||||
: data.metric.name,
|
||||
value:
|
||||
p.dataKey === data.dailyCost.dataKey
|
||||
? formatGraphValue(Number(p.value), i, data.dailyCost.format)
|
||||
: formatGraphValue(Number(p.value), i, data.metric.format),
|
||||
? formatGraphValueWith(Number(p.value), i, data.dailyCost.format)
|
||||
: formatGraphValueWith(Number(p.value), i, data.metric.format),
|
||||
fill:
|
||||
p.dataKey === data.dailyCost.dataKey
|
||||
? theme.palette.blue
|
||||
@@ -160,7 +163,7 @@ export const CostOverviewChart = ({
|
||||
<YAxis
|
||||
domain={[() => 0, 'dataMax']}
|
||||
tick={{ fill: styles.axis.fill }}
|
||||
tickFormatter={formatGraphValue}
|
||||
tickFormatter={formatGraphValue(baseCurrency)}
|
||||
width={styles.yAxis.width}
|
||||
yAxisId={data.dailyCost.dataKey}
|
||||
/>
|
||||
|
||||
+15
-10
@@ -19,6 +19,7 @@ import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { ProductEntityDialog } from './ProductEntityDialog';
|
||||
import { render } from '@testing-library/react';
|
||||
import { Entity } from '../../types';
|
||||
import { MockConfigProvider } from '../../testUtils';
|
||||
|
||||
const atomicEntity: Entity = {
|
||||
id: null,
|
||||
@@ -86,11 +87,13 @@ describe('<ProductEntityDialog/>', () => {
|
||||
it('Should show a tab for a single sub-entity type', () => {
|
||||
const { getByText } = render(
|
||||
wrapInTestApp(
|
||||
<ProductEntityDialog
|
||||
open
|
||||
entity={singleBreakdownEntity}
|
||||
onClose={jest.fn()}
|
||||
/>,
|
||||
<MockConfigProvider>
|
||||
<ProductEntityDialog
|
||||
open
|
||||
entity={singleBreakdownEntity}
|
||||
onClose={jest.fn()}
|
||||
/>
|
||||
</MockConfigProvider>,
|
||||
),
|
||||
);
|
||||
expect(getByText('Breakdown by SKU')).toBeInTheDocument();
|
||||
@@ -99,11 +102,13 @@ describe('<ProductEntityDialog/>', () => {
|
||||
it('Should show tabs when multiple sub-entity types exist', () => {
|
||||
const { getByText } = render(
|
||||
wrapInTestApp(
|
||||
<ProductEntityDialog
|
||||
open
|
||||
entity={multiBreakdownEntity}
|
||||
onClose={jest.fn()}
|
||||
/>,
|
||||
<MockConfigProvider>
|
||||
<ProductEntityDialog
|
||||
open
|
||||
entity={multiBreakdownEntity}
|
||||
onClose={jest.fn()}
|
||||
/>
|
||||
</MockConfigProvider>,
|
||||
),
|
||||
);
|
||||
expect(getByText('Breakdown by SKU')).toBeInTheDocument();
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import { costFormatter, formatChange } from '../../utils/formatters';
|
||||
import { formatChange } from '../../utils/formatters';
|
||||
import { useEntityDialogStyles as useStyles } from '../../utils/styles';
|
||||
import { CostGrowthIndicator } from '../CostGrowth';
|
||||
import { BarChartOptions, ChangeStatistic, Entity } from '../../types';
|
||||
import { Table, TableColumn } from '@backstage/core-components';
|
||||
import { useConfig } from '../../hooks';
|
||||
|
||||
export type ProductEntityTableOptions = Partial<
|
||||
Pick<BarChartOptions, 'previousName' | 'currentName'>
|
||||
@@ -35,36 +36,38 @@ type RowData = {
|
||||
change: ChangeStatistic;
|
||||
};
|
||||
|
||||
function createRenderer(col: keyof RowData, classes: Record<string, string>) {
|
||||
return function render(rowData: {}): JSX.Element {
|
||||
const row = rowData as RowData;
|
||||
const rowStyles = classnames(classes.row, {
|
||||
[classes.rowTotal]: row.id === 'total',
|
||||
[classes.colFirst]: col === 'label',
|
||||
[classes.colLast]: col === 'change',
|
||||
});
|
||||
const createRenderer =
|
||||
(baseCurrency: Intl.NumberFormat) =>
|
||||
(col: keyof RowData, classes: Record<string, string>) => {
|
||||
return function render(rowData: {}): JSX.Element {
|
||||
const row = rowData as RowData;
|
||||
const rowStyles = classnames(classes.row, {
|
||||
[classes.rowTotal]: row.id === 'total',
|
||||
[classes.colFirst]: col === 'label',
|
||||
[classes.colLast]: col === 'change',
|
||||
});
|
||||
|
||||
switch (col) {
|
||||
case 'previous':
|
||||
case 'current':
|
||||
return (
|
||||
<Typography className={rowStyles}>
|
||||
{costFormatter.format(row[col])}
|
||||
</Typography>
|
||||
);
|
||||
case 'change':
|
||||
return (
|
||||
<CostGrowthIndicator
|
||||
className={rowStyles}
|
||||
change={row.change}
|
||||
formatter={formatChange}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <Typography className={rowStyles}>{row.label}</Typography>;
|
||||
}
|
||||
switch (col) {
|
||||
case 'previous':
|
||||
case 'current':
|
||||
return (
|
||||
<Typography className={rowStyles}>
|
||||
{baseCurrency.format(row[col])}
|
||||
</Typography>
|
||||
);
|
||||
case 'change':
|
||||
return (
|
||||
<CostGrowthIndicator
|
||||
className={rowStyles}
|
||||
change={row.change}
|
||||
formatter={formatChange}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <Typography className={rowStyles}>{row.label}</Typography>;
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// material-table does not support fixed rows. Override the sorting algorithm
|
||||
// to force Total row to bottom by default or when a user sort toggles a column.
|
||||
@@ -99,6 +102,7 @@ export const ProductEntityTable = ({
|
||||
options,
|
||||
}: ProductEntityTableProps) => {
|
||||
const classes = useStyles();
|
||||
const { baseCurrency } = useConfig();
|
||||
const entities = entity.entities[entityLabel];
|
||||
|
||||
const data = Object.assign(
|
||||
@@ -116,7 +120,7 @@ export const ProductEntityTable = ({
|
||||
{
|
||||
field: 'label',
|
||||
title: <Typography className={firstColClasses}>{entityLabel}</Typography>,
|
||||
render: createRenderer('label', classes),
|
||||
render: createRenderer(baseCurrency)('label', classes),
|
||||
customSort: createSorter('label'),
|
||||
width: '33.33%',
|
||||
},
|
||||
@@ -126,7 +130,7 @@ export const ProductEntityTable = ({
|
||||
<Typography className={classes.column}>{data.previousName}</Typography>
|
||||
),
|
||||
align: 'right',
|
||||
render: createRenderer('previous', classes),
|
||||
render: createRenderer(baseCurrency)('previous', classes),
|
||||
customSort: createSorter('previous'),
|
||||
},
|
||||
{
|
||||
@@ -135,14 +139,14 @@ export const ProductEntityTable = ({
|
||||
<Typography className={classes.column}>{data.currentName}</Typography>
|
||||
),
|
||||
align: 'right',
|
||||
render: createRenderer('current', classes),
|
||||
render: createRenderer(baseCurrency)('current', classes),
|
||||
customSort: createSorter('current'),
|
||||
},
|
||||
{
|
||||
field: 'change',
|
||||
title: <Typography className={lastColClasses}>Change</Typography>,
|
||||
align: 'right',
|
||||
render: createRenderer('change', classes),
|
||||
render: createRenderer(baseCurrency)('change', classes),
|
||||
customSort: createSorter('change'),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -50,7 +50,8 @@ import {
|
||||
} from '../../utils/styles';
|
||||
import { Duration, Entity, Maybe } from '../../types';
|
||||
import { choose } from '../../utils/change';
|
||||
import { TooltipRenderer } from '../../types/Tooltip';
|
||||
import { TooltipRenderer } from '../../types';
|
||||
import { useConfig } from '../../hooks';
|
||||
|
||||
export type ProductInsightsChartProps = {
|
||||
billingDate: string;
|
||||
@@ -65,6 +66,7 @@ export const ProductInsightsChart = ({
|
||||
}: ProductInsightsChartProps) => {
|
||||
const classes = useStyles();
|
||||
const layoutClasses = useLayoutStyles();
|
||||
const { baseCurrency } = useConfig();
|
||||
|
||||
// Only a single entities Record for the root product entity is supported
|
||||
const entities = useMemo(() => {
|
||||
@@ -131,7 +133,7 @@ export const ProductInsightsChart = ({
|
||||
const id = label === '' ? null : label;
|
||||
|
||||
const title = titleOf(label);
|
||||
const items = payload.map(tooltipItemOf).filter(notEmpty);
|
||||
const items = payload.map(tooltipItemOf(baseCurrency)).filter(notEmpty);
|
||||
|
||||
const activeEntity = findAlways(entities, e => e.id === id);
|
||||
const breakdowns = Object.keys(activeEntity.entities);
|
||||
|
||||
+11
-6
@@ -19,6 +19,7 @@ import { UnlabeledDataflowAlertCard } from './UnlabeledDataflowAlertCard';
|
||||
import {
|
||||
createMockUnlabeledDataflowData,
|
||||
createMockUnlabeledDataflowAlertProject,
|
||||
MockConfigProvider,
|
||||
} from '../../testUtils';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
@@ -61,9 +62,11 @@ describe('<UnlabeledDataflowAlertCard />', () => {
|
||||
'projects with unlabeled Dataflow jobs in the last 30 days.',
|
||||
);
|
||||
const rendered = await renderInTestApp(
|
||||
<UnlabeledDataflowAlertCard
|
||||
alert={MockUnlabeledDataflowAlertMultipleProjects}
|
||||
/>,
|
||||
<MockConfigProvider>
|
||||
<UnlabeledDataflowAlertCard
|
||||
alert={MockUnlabeledDataflowAlertMultipleProjects}
|
||||
/>
|
||||
</MockConfigProvider>,
|
||||
);
|
||||
expect(rendered.getByText(subheader)).toBeInTheDocument();
|
||||
});
|
||||
@@ -71,9 +74,11 @@ describe('<UnlabeledDataflowAlertCard />', () => {
|
||||
it('renders the correct subheader for a single project', async () => {
|
||||
const subheader = new RegExp('1 project');
|
||||
const rendered = await renderInTestApp(
|
||||
<UnlabeledDataflowAlertCard
|
||||
alert={MockUnlabeledDataflowAlertSingleProject}
|
||||
/>,
|
||||
<MockConfigProvider>
|
||||
<UnlabeledDataflowAlertCard
|
||||
alert={MockUnlabeledDataflowAlertSingleProject}
|
||||
/>
|
||||
</MockConfigProvider>,
|
||||
);
|
||||
expect(rendered.getByText(subheader)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ import { Config as BackstageConfig } from '@backstage/config';
|
||||
import { Currency, Icon, Metric, Product } from '../types';
|
||||
import { getIcon } from '../utils/navigation';
|
||||
import { validateCurrencies, validateMetrics } from '../utils/config';
|
||||
import { defaultCurrencies } from '../utils/currency';
|
||||
import { createCurrencyFormat, defaultCurrencies } from '../utils/currency';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
/*
|
||||
@@ -46,6 +46,11 @@ import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
* default: true
|
||||
* metricB:
|
||||
* name: Metric B
|
||||
* baseCurrency:
|
||||
* locale: nl-NL
|
||||
* options:
|
||||
* currency: EUR
|
||||
* minimumFractionDigits: 3
|
||||
* currencies:
|
||||
* currencyA:
|
||||
* label: Currency A
|
||||
@@ -60,6 +65,7 @@ import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
/** @public */
|
||||
export type ConfigContextProps = {
|
||||
baseCurrency: Intl.NumberFormat;
|
||||
metrics: Metric[];
|
||||
products: Product[];
|
||||
icons: Icon[];
|
||||
@@ -72,6 +78,7 @@ export const ConfigContext = createContext<ConfigContextProps | undefined>(
|
||||
);
|
||||
|
||||
const defaultState: ConfigContextProps = {
|
||||
baseCurrency: createCurrencyFormat(),
|
||||
metrics: [],
|
||||
products: [],
|
||||
icons: [],
|
||||
@@ -110,6 +117,42 @@ export const ConfigProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
return [];
|
||||
}
|
||||
|
||||
function getBaseCurrency(): Intl.NumberFormat {
|
||||
const baseCurrency = c.getOptionalConfig('costInsights.baseCurrency');
|
||||
if (baseCurrency) {
|
||||
const options = baseCurrency.getOptionalConfig('options');
|
||||
return new Intl.NumberFormat(
|
||||
baseCurrency.getOptionalString('locale'),
|
||||
options
|
||||
? {
|
||||
localeMatcher: options.getOptionalString('localeMatcher'),
|
||||
style: 'currency',
|
||||
currency: options.getOptionalString('currency'),
|
||||
currencySign: options.getOptionalString('currencySign'),
|
||||
useGrouping: options.getOptionalBoolean('useGrouping'),
|
||||
minimumIntegerDigits: options.getOptionalNumber(
|
||||
'minimumIntegerDigits',
|
||||
),
|
||||
minimumFractionDigits: options.getOptionalNumber(
|
||||
'minimumFractionDigits',
|
||||
),
|
||||
maximumFractionDigits: options.getOptionalNumber(
|
||||
'maximumFractionDigits',
|
||||
),
|
||||
minimumSignificantDigits: options.getOptionalNumber(
|
||||
'minimumSignificantDigits',
|
||||
),
|
||||
maximumSignificantDigits: options.getOptionalNumber(
|
||||
'maximumSignificantDigits',
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
}
|
||||
|
||||
return defaultState.baseCurrency;
|
||||
}
|
||||
|
||||
function getCurrencies(): Currency[] {
|
||||
const currencies = c.getOptionalConfig('costInsights.currencies');
|
||||
if (currencies) {
|
||||
@@ -141,6 +184,7 @@ export const ConfigProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
const baseCurrency = getBaseCurrency();
|
||||
const products = getProducts();
|
||||
const metrics = getMetrics();
|
||||
const engineerCost = getEngineerCost();
|
||||
@@ -152,6 +196,7 @@ export const ConfigProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
|
||||
setConfig(prevState => ({
|
||||
...prevState,
|
||||
baseCurrency,
|
||||
metrics,
|
||||
products,
|
||||
engineerCost,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user