Compare commits
55 Commits
@backstage
...
main
Author | SHA1 | Date |
---|---|---|
|
6a65a4cea3 | |
|
c2261cf6ab | |
|
727c25df51 | |
|
ff785e582c | |
|
dcffece576 | |
|
ceeadd1e1a | |
|
7bafbd4fc8 | |
|
0493206694 | |
|
ca3c813046 | |
|
8367480d11 | |
|
6e6b88413f | |
|
4815dfd531 | |
|
c2b33a16aa | |
|
56382c80de | |
|
7043b8bbb6 | |
|
8460759070 | |
|
02546adb32 | |
|
c4f95b6e1e | |
|
fe6d855d73 | |
|
e46f2a55c2 | |
|
46e1cefbac | |
|
deb952366a | |
|
593523a511 | |
|
4d645136fd | |
|
496b5ea76f | |
|
2e3afacf8b | |
|
80b21b20f0 | |
|
3cd6bac697 | |
|
707c5f303c | |
|
7df058feae | |
|
00731dafe6 | |
|
51814e6f58 | |
|
91f611bccb | |
|
74876f0d13 | |
|
fb6cbc705d | |
|
56b4264d5f | |
|
b0e72b5547 | |
|
2c0ec89454 | |
|
f5fff6fc37 | |
|
bff3611105 | |
|
13c0def6ea | |
|
fa109420d0 | |
|
d1b75248ed | |
|
45236345e3 | |
|
2c67eaaa4a | |
|
4b2153a833 | |
|
e172f18500 | |
|
10978474b7 | |
|
63407d09df | |
|
43064bf46c | |
|
f9a8eec66d | |
|
1d51b5654e | |
|
7593e172ce | |
|
9a143944f3 | |
|
a04f9dbbfe |
|
@ -13,6 +13,7 @@ yarn.lock @backsta
|
|||
/workspaces/acr @backstage/community-plugins-maintainers @christoph-jerolimov @ciiay @debsmita1 @divyanshiGupta @its-mitesh-kumar @logonoff
|
||||
/workspaces/acs @backstage/community-plugins-maintainers @sachaudh @alwayshooin @dvail @maknop
|
||||
/workspaces/adr @backstage/community-plugins-maintainers @kuangp
|
||||
/workspaces/agent-forge @backstage/community-plugins-maintainers @sbraicu @sriaradhyula @subbaksh @suwhang-cisco
|
||||
/workspaces/airbrake @backstage/community-plugins-maintainers
|
||||
/workspaces/allure @backstage/community-plugins-maintainers
|
||||
/workspaces/amplication @backstage/community-plugins-maintainers @itainathaniel
|
||||
|
@ -31,6 +32,7 @@ yarn.lock @backsta
|
|||
/workspaces/bazaar @backstage/community-plugins-maintainers
|
||||
/workspaces/bitrise @backstage/community-plugins-maintainers @backstage/sda-se-reviewers
|
||||
/workspaces/blackduck @backstage/community-plugins-maintainers @deepan10
|
||||
/workspaces/bookmarks @backstage/community-plugins-maintainers @logonoff @christoph-jerolimov @ciiay @debsmita1 @divyanshiGupta @its-mitesh-kumar @lokanandaprabhu
|
||||
/workspaces/cicd-statistics @backstage/community-plugins-maintainers
|
||||
/workspaces/cloudbuild @backstage/community-plugins-maintainers
|
||||
/workspaces/code-climate @backstage/community-plugins-maintainers
|
||||
|
@ -79,7 +81,7 @@ yarn.lock @backsta
|
|||
/workspaces/nexus-repository-manager @backstage/community-plugins-maintainers @ciiay @debsmita1 @jessicajhee
|
||||
/workspaces/nomad @backstage/community-plugins-maintainers
|
||||
/workspaces/noop @backstage/community-plugins-maintainers
|
||||
/workspaces/npm @backstage/community-plugins-maintainers @christoph-jerolimov @ciiay @karthikjeeyar
|
||||
/workspaces/npm @backstage/community-plugins-maintainers @christoph-jerolimov @ciiay @karthikjeeyar @logonoff
|
||||
/workspaces/ocm @backstage/community-plugins-maintainers @christoph-jerolimov @ciiay @debsmita1 @divyanshiGupta @its-mitesh-kumar @logonoff
|
||||
/workspaces/octopus-deploy @backstage/community-plugins-maintainers @jmezach
|
||||
/workspaces/odo @backstage/community-plugins-maintainers
|
||||
|
|
|
@ -33,6 +33,7 @@ body:
|
|||
- bazaar
|
||||
- bitrise
|
||||
- blackduck
|
||||
- bookmarks
|
||||
- cicd-statistics
|
||||
- cloudbuild
|
||||
- code-climate
|
||||
|
|
|
@ -34,6 +34,7 @@ body:
|
|||
- bazaar
|
||||
- bitrise
|
||||
- blackduck
|
||||
- bookmarks
|
||||
- cicd-statistics
|
||||
- cloudbuild
|
||||
- code-climate
|
||||
|
|
|
@ -24,6 +24,7 @@ attributes:
|
|||
- bazaar
|
||||
- bitrise
|
||||
- blackduck
|
||||
- bookmarks
|
||||
- cicd-statistics
|
||||
- cloudbuild
|
||||
- code-climate
|
||||
|
|
|
@ -58,6 +58,9 @@ workspace/bitrise:
|
|||
workspace/blackduck:
|
||||
- "Workspace\\s*blackduck"
|
||||
|
||||
workspace/bookmarks:
|
||||
- "Workspace\\s*bookmarks"
|
||||
|
||||
workspace/cicd-statistics:
|
||||
- "Workspace\\s*cicd-statistics"
|
||||
|
||||
|
|
|
@ -78,6 +78,13 @@ jobs:
|
|||
- name: 'Set release name'
|
||||
id: set_release_name
|
||||
run: node scripts/ci/set-release-name.js ${{ matrix.workspace }} ${{ inputs.release_line || 'main' }}
|
||||
- name: 'Check current and release versions'
|
||||
id: check
|
||||
run: |
|
||||
if [[ "${{ steps.set_release_name.outputs.release_version }}" == "${{ steps.set_release_name.outputs.current_version }}" ]]; then
|
||||
echo "Backstage release version and current workspace version are the same, skipping version bump"
|
||||
exit 1 # Non-zero exit code fails the step and job
|
||||
fi
|
||||
- name: 'Configure git'
|
||||
run: |
|
||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
@ -97,7 +104,7 @@ jobs:
|
|||
YARN_ENABLE_IMMUTABLE_INSTALLS: false
|
||||
- name: Run dedupe
|
||||
working-directory: ./workspaces/${{ matrix.workspace }}
|
||||
run: yarn dedupe
|
||||
run: yarn dedupe
|
||||
- name: 'Check for changes'
|
||||
id: check_for_changes
|
||||
run: |
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
"eslint-plugin-notice": "^0.9.10",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-testing-library": "^6.0.0",
|
||||
"fs-extra": "11.3.0",
|
||||
"fs-extra": "11.3.1",
|
||||
"husky": "^9.0.11",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lint-staged": "^15.2.2",
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@backstage-community/plugin-acr': patch
|
||||
---
|
||||
|
||||
Updated dependency `@testing-library/jest-dom` to `6.7.0`.
|
|
@ -62,7 +62,7 @@
|
|||
"@backstage/core-app-api": "^1.18.0",
|
||||
"@backstage/dev-utils": "^1.1.12",
|
||||
"@backstage/test-utils": "^1.7.10",
|
||||
"@testing-library/jest-dom": "6.6.4",
|
||||
"@testing-library/jest-dom": "6.7.0",
|
||||
"@testing-library/react": "14.3.1",
|
||||
"@testing-library/react-hooks": "8.0.1",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
|
|
|
@ -2725,7 +2725,7 @@ __metadata:
|
|||
"@backstage/theme": "npm:^0.6.7"
|
||||
"@material-ui/core": "npm:^4.9.13"
|
||||
"@material-ui/icons": "npm:^4.11.3"
|
||||
"@testing-library/jest-dom": "npm:6.6.4"
|
||||
"@testing-library/jest-dom": "npm:6.7.0"
|
||||
"@testing-library/react": "npm:14.3.1"
|
||||
"@testing-library/react-hooks": "npm:8.0.1"
|
||||
"@testing-library/user-event": "npm:14.6.1"
|
||||
|
@ -11690,18 +11690,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@testing-library/jest-dom@npm:6.6.4, @testing-library/jest-dom@npm:^6.0.0":
|
||||
version: 6.6.4
|
||||
resolution: "@testing-library/jest-dom@npm:6.6.4"
|
||||
"@testing-library/jest-dom@npm:6.7.0, @testing-library/jest-dom@npm:^6.0.0":
|
||||
version: 6.7.0
|
||||
resolution: "@testing-library/jest-dom@npm:6.7.0"
|
||||
dependencies:
|
||||
"@adobe/css-tools": "npm:^4.4.0"
|
||||
aria-query: "npm:^5.0.0"
|
||||
css.escape: "npm:^1.5.1"
|
||||
dom-accessibility-api: "npm:^0.6.3"
|
||||
lodash: "npm:^4.17.21"
|
||||
picocolors: "npm:^1.1.1"
|
||||
redent: "npm:^3.0.0"
|
||||
checksum: 10/5e67112c789f884fb75b279c2cddfdd0995a012a7847a03c474e4134f0d213934ee70c97433bca26b45e3a5ffa56faafe6499c8e57841179c4f2bd80eef429cd
|
||||
checksum: 10/c994f028b6f2d49c18c9fd6050af7f3316fb0afd03d0ba15d03b177f0f046a0308302dd52ab289fad8794e16a88e4d724b5f23caa007cf343a4b5e435efb84d9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.2.9",
|
||||
"@backstage/plugin-catalog-backend-module-unprocessed": "^0.6.1",
|
||||
"@backstage/plugin-scaffolder": "^1.32.0",
|
||||
"@backstage/plugin-scaffolder-backend": "^2.0.0",
|
||||
"@backstage/plugin-scaffolder-backend": "^2.1.1",
|
||||
"@backstage/repo-tools": "^0.14.0",
|
||||
"@changesets/cli": "^2.28.1",
|
||||
"@eslint/js": "^9.19.0",
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
"@backstage/plugin-permission-node": "^0.10.1",
|
||||
"@backstage/plugin-proxy-backend": "^0.6.3",
|
||||
"@backstage/plugin-scaffolder": "^1.32.0",
|
||||
"@backstage/plugin-scaffolder-backend": "^2.0.0",
|
||||
"@backstage/plugin-scaffolder-backend": "^2.1.1",
|
||||
"@backstage/plugin-search-backend": "^2.0.3",
|
||||
"@backstage/plugin-search-backend-module-catalog": "^0.3.5",
|
||||
"@backstage/plugin-search-backend-module-pg": "^0.5.45",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'@backstage-community/plugin-analytics-module-matomo': patch
|
||||
'@backstage-community/plugin-analytics-provider-segment': patch
|
||||
---
|
||||
|
||||
Updated dependency `@testing-library/jest-dom` to `6.7.0`.
|
|
@ -44,7 +44,7 @@
|
|||
"@backstage/core-app-api": "^1.17.1",
|
||||
"@backstage/core-components": "^0.17.3",
|
||||
"@backstage/dev-utils": "^1.1.11",
|
||||
"@testing-library/jest-dom": "6.6.4",
|
||||
"@testing-library/jest-dom": "6.7.0",
|
||||
"@types/node": "22.15.29",
|
||||
"cross-fetch": "4.0.0",
|
||||
"msw": "1.3.5"
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
"@backstage/dev-utils": "^1.1.11",
|
||||
"@backstage/test-utils": "^1.7.9",
|
||||
"@testing-library/dom": "9.3.4",
|
||||
"@testing-library/jest-dom": "6.6.4",
|
||||
"@testing-library/jest-dom": "6.7.0",
|
||||
"@testing-library/react": "14.3.1",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@types/node": "22.15.29",
|
||||
|
|
|
@ -1791,7 +1791,7 @@ __metadata:
|
|||
"@backstage/core-components": "npm:^0.17.3"
|
||||
"@backstage/core-plugin-api": "npm:^1.10.8"
|
||||
"@backstage/dev-utils": "npm:^1.1.11"
|
||||
"@testing-library/jest-dom": "npm:6.6.4"
|
||||
"@testing-library/jest-dom": "npm:6.7.0"
|
||||
"@types/node": "npm:22.15.29"
|
||||
cross-fetch: "npm:4.0.0"
|
||||
msw: "npm:1.3.5"
|
||||
|
@ -1838,7 +1838,7 @@ __metadata:
|
|||
"@material-ui/lab": "npm:4.0.0-alpha.61"
|
||||
"@segment/analytics-next": "npm:^1.58.0"
|
||||
"@testing-library/dom": "npm:9.3.4"
|
||||
"@testing-library/jest-dom": "npm:6.6.4"
|
||||
"@testing-library/jest-dom": "npm:6.7.0"
|
||||
"@testing-library/react": "npm:14.3.1"
|
||||
"@testing-library/user-event": "npm:14.6.1"
|
||||
"@types/node": "npm:22.15.29"
|
||||
|
@ -6491,18 +6491,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@testing-library/jest-dom@npm:6.6.4, @testing-library/jest-dom@npm:^6.0.0":
|
||||
version: 6.6.4
|
||||
resolution: "@testing-library/jest-dom@npm:6.6.4"
|
||||
"@testing-library/jest-dom@npm:6.7.0, @testing-library/jest-dom@npm:^6.0.0":
|
||||
version: 6.7.0
|
||||
resolution: "@testing-library/jest-dom@npm:6.7.0"
|
||||
dependencies:
|
||||
"@adobe/css-tools": "npm:^4.4.0"
|
||||
aria-query: "npm:^5.0.0"
|
||||
css.escape: "npm:^1.5.1"
|
||||
dom-accessibility-api: "npm:^0.6.3"
|
||||
lodash: "npm:^4.17.21"
|
||||
picocolors: "npm:^1.1.1"
|
||||
redent: "npm:^3.0.0"
|
||||
checksum: 10/5e67112c789f884fb75b279c2cddfdd0995a012a7847a03c474e4134f0d213934ee70c97433bca26b45e3a5ffa56faafe6499c8e57841179c4f2bd80eef429cd
|
||||
checksum: 10/c994f028b6f2d49c18c9fd6050af7f3316fb0afd03d0ba15d03b177f0f046a0308302dd52ab289fad8794e16a88e4d724b5f23caa007cf343a4b5e435efb84d9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,4 @@
|
|||
plugins:
|
||||
- checksum: 8af7b3f2d7d19cacc7a3712f871efcb6208ba283a1f532260b0cba80c2cb66ed772b207b5ba41b8c5d64dd8d5e0c0e15bbb445bd14afac491712965211ba027c
|
||||
path: .yarn/plugins/@yarnpkg/plugin-backstage.cjs
|
||||
spec: 'https://versions.backstage.io/v1/releases/1.42.3/yarn-plugin'
|
|
@ -1,6 +1,7 @@
|
|||
app:
|
||||
title: Azure DevOps Example App
|
||||
baseUrl: http://localhost:3000
|
||||
packages: all
|
||||
|
||||
organization:
|
||||
name: Azure DevOps Example
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"version": "1.41.1"
|
||||
"version": "1.42.3"
|
||||
}
|
||||
|
|
|
@ -36,9 +36,9 @@
|
|||
"directory": "workspaces/azure-devops"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.33.1",
|
||||
"@backstage/e2e-test-utils": "^0.1.1",
|
||||
"@backstage/repo-tools": "^0.15.0",
|
||||
"@backstage/cli": "backstage:^",
|
||||
"@backstage/e2e-test-utils": "backstage:^",
|
||||
"@backstage/repo-tools": "backstage:^",
|
||||
"@changesets/cli": "^2.27.1",
|
||||
"knip": "^5.27.4",
|
||||
"node-gyp": "^10.0.0",
|
||||
|
@ -58,9 +58,5 @@
|
|||
"*.{json,md}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage-community/plugin-azure-devops": "workspace:^",
|
||||
"@backstage-community/plugin-azure-devops-backend": "workspace:^"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,29 +20,29 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@backstage-community/plugin-azure-devops": "workspace:^",
|
||||
"@backstage/app-defaults": "^1.6.4",
|
||||
"@backstage/catalog-model": "^1.7.5",
|
||||
"@backstage/cli": "^0.33.1",
|
||||
"@backstage/core-app-api": "^1.18.0",
|
||||
"@backstage/core-components": "^0.17.4",
|
||||
"@backstage/core-plugin-api": "^1.10.9",
|
||||
"@backstage/integration-react": "^1.2.9",
|
||||
"@backstage/plugin-api-docs": "^0.12.9",
|
||||
"@backstage/plugin-catalog": "^1.31.1",
|
||||
"@backstage/plugin-catalog-common": "^1.1.5",
|
||||
"@backstage/plugin-catalog-graph": "^0.4.21",
|
||||
"@backstage/plugin-catalog-import": "^0.13.3",
|
||||
"@backstage/plugin-catalog-react": "^1.19.1",
|
||||
"@backstage/plugin-org": "^0.6.41",
|
||||
"@backstage/plugin-permission-react": "^0.4.36",
|
||||
"@backstage/plugin-scaffolder": "^1.33.0",
|
||||
"@backstage/plugin-search": "^1.4.28",
|
||||
"@backstage/plugin-search-react": "^1.9.2",
|
||||
"@backstage/plugin-techdocs": "^1.13.2",
|
||||
"@backstage/plugin-techdocs-module-addons-contrib": "^1.1.26",
|
||||
"@backstage/plugin-techdocs-react": "^1.3.1",
|
||||
"@backstage/plugin-user-settings": "^0.8.24",
|
||||
"@backstage/theme": "^0.6.7",
|
||||
"@backstage/app-defaults": "backstage:^",
|
||||
"@backstage/catalog-model": "backstage:^",
|
||||
"@backstage/cli": "backstage:^",
|
||||
"@backstage/core-app-api": "backstage:^",
|
||||
"@backstage/core-components": "backstage:^",
|
||||
"@backstage/core-plugin-api": "backstage:^",
|
||||
"@backstage/integration-react": "backstage:^",
|
||||
"@backstage/plugin-api-docs": "backstage:^",
|
||||
"@backstage/plugin-catalog": "backstage:^",
|
||||
"@backstage/plugin-catalog-common": "backstage:^",
|
||||
"@backstage/plugin-catalog-graph": "backstage:^",
|
||||
"@backstage/plugin-catalog-import": "backstage:^",
|
||||
"@backstage/plugin-catalog-react": "backstage:^",
|
||||
"@backstage/plugin-org": "backstage:^",
|
||||
"@backstage/plugin-permission-react": "backstage:^",
|
||||
"@backstage/plugin-scaffolder": "backstage:^",
|
||||
"@backstage/plugin-search": "backstage:^",
|
||||
"@backstage/plugin-search-react": "backstage:^",
|
||||
"@backstage/plugin-techdocs": "backstage:^",
|
||||
"@backstage/plugin-techdocs-module-addons-contrib": "backstage:^",
|
||||
"@backstage/plugin-techdocs-react": "backstage:^",
|
||||
"@backstage/plugin-user-settings": "backstage:^",
|
||||
"@backstage/theme": "backstage:^",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"react": "^18.0.2",
|
||||
|
@ -52,7 +52,7 @@
|
|||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/test-utils": "^1.7.10",
|
||||
"@backstage/test-utils": "backstage:^",
|
||||
"@playwright/test": "^1.32.3",
|
||||
"@testing-library/dom": "^9.0.0",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
|
|
|
@ -25,26 +25,26 @@
|
|||
"@backstage-community/plugin-catalog-backend-module-azure-devops-annotator-processor": "workspace:^",
|
||||
"@backstage-community/plugin-scaffolder-backend-module-azure-devops": "workspace:^",
|
||||
"@backstage-community/plugin-scaffolder-backend-module-dotnet": "workspace:^",
|
||||
"@backstage/backend-defaults": "^0.11.1",
|
||||
"@backstage/config": "^1.3.3",
|
||||
"@backstage/plugin-app-backend": "^0.5.4",
|
||||
"@backstage/plugin-auth-backend": "^0.25.2",
|
||||
"@backstage/plugin-auth-backend-module-guest-provider": "^0.2.10",
|
||||
"@backstage/plugin-auth-node": "^0.6.5",
|
||||
"@backstage/plugin-catalog-backend": "^3.0.0",
|
||||
"@backstage/plugin-catalog-backend-module-logs": "^0.1.12",
|
||||
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.2.10",
|
||||
"@backstage/plugin-permission-backend": "^0.7.2",
|
||||
"@backstage/plugin-permission-backend-module-allow-all-policy": "^0.2.10",
|
||||
"@backstage/plugin-permission-common": "^0.9.1",
|
||||
"@backstage/plugin-permission-node": "^0.10.2",
|
||||
"@backstage/plugin-proxy-backend": "^0.6.4",
|
||||
"@backstage/plugin-scaffolder-backend": "^2.1.0",
|
||||
"@backstage/plugin-search-backend": "^2.0.4",
|
||||
"@backstage/plugin-search-backend-module-catalog": "^0.3.6",
|
||||
"@backstage/plugin-search-backend-module-techdocs": "^0.4.4",
|
||||
"@backstage/plugin-search-backend-node": "^1.3.13",
|
||||
"@backstage/plugin-techdocs-backend": "^2.0.4",
|
||||
"@backstage/backend-defaults": "backstage:^",
|
||||
"@backstage/config": "backstage:^",
|
||||
"@backstage/plugin-app-backend": "backstage:^",
|
||||
"@backstage/plugin-auth-backend": "backstage:^",
|
||||
"@backstage/plugin-auth-backend-module-guest-provider": "backstage:^",
|
||||
"@backstage/plugin-auth-node": "backstage:^",
|
||||
"@backstage/plugin-catalog-backend": "backstage:^",
|
||||
"@backstage/plugin-catalog-backend-module-logs": "backstage:^",
|
||||
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "backstage:^",
|
||||
"@backstage/plugin-permission-backend": "backstage:^",
|
||||
"@backstage/plugin-permission-backend-module-allow-all-policy": "backstage:^",
|
||||
"@backstage/plugin-permission-common": "backstage:^",
|
||||
"@backstage/plugin-permission-node": "backstage:^",
|
||||
"@backstage/plugin-proxy-backend": "backstage:^",
|
||||
"@backstage/plugin-scaffolder-backend": "backstage:^",
|
||||
"@backstage/plugin-search-backend": "backstage:^",
|
||||
"@backstage/plugin-search-backend-module-catalog": "backstage:^",
|
||||
"@backstage/plugin-search-backend-module-techdocs": "backstage:^",
|
||||
"@backstage/plugin-search-backend-node": "backstage:^",
|
||||
"@backstage/plugin-techdocs-backend": "backstage:^",
|
||||
"app": "link:../app",
|
||||
"better-sqlite3": "^9.0.0",
|
||||
"dockerode": "^3.3.1",
|
||||
|
@ -53,7 +53,7 @@
|
|||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.33.1"
|
||||
"@backstage/cli": "backstage:^"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
|
|
@ -1,5 +1,16 @@
|
|||
# @backstage-community/plugin-azure-devops-backend
|
||||
|
||||
## 0.20.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 8367480: Backstage version bump to v1.42.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [8367480]
|
||||
- @backstage-community/plugin-azure-devops-common@0.14.0
|
||||
|
||||
## 0.19.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@backstage-community/plugin-azure-devops-backend",
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
@ -35,17 +35,17 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@backstage-community/plugin-azure-devops-common": "workspace:^",
|
||||
"@backstage/backend-defaults": "^0.11.1",
|
||||
"@backstage/backend-plugin-api": "^1.4.1",
|
||||
"@backstage/catalog-model": "^1.7.5",
|
||||
"@backstage/config": "^1.3.3",
|
||||
"@backstage/errors": "^1.2.7",
|
||||
"@backstage/integration": "^1.17.1",
|
||||
"@backstage/plugin-auth-node": "^0.6.5",
|
||||
"@backstage/plugin-catalog-common": "^1.1.5",
|
||||
"@backstage/plugin-catalog-node": "^1.17.2",
|
||||
"@backstage/plugin-permission-common": "^0.9.1",
|
||||
"@backstage/plugin-permission-node": "^0.10.2",
|
||||
"@backstage/backend-defaults": "backstage:^",
|
||||
"@backstage/backend-plugin-api": "backstage:^",
|
||||
"@backstage/catalog-model": "backstage:^",
|
||||
"@backstage/config": "backstage:^",
|
||||
"@backstage/errors": "backstage:^",
|
||||
"@backstage/integration": "backstage:^",
|
||||
"@backstage/plugin-auth-node": "backstage:^",
|
||||
"@backstage/plugin-catalog-common": "backstage:^",
|
||||
"@backstage/plugin-catalog-node": "backstage:^",
|
||||
"@backstage/plugin-permission-common": "backstage:^",
|
||||
"@backstage/plugin-permission-node": "backstage:^",
|
||||
"@types/express": "^4.17.6",
|
||||
"azure-devops-node-api": "^13.0.0",
|
||||
"express": "^4.17.1",
|
||||
|
@ -54,8 +54,8 @@
|
|||
"p-limit": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "^1.7.0",
|
||||
"@backstage/cli": "^0.33.1",
|
||||
"@backstage/backend-test-utils": "backstage:^",
|
||||
"@backstage/cli": "backstage:^",
|
||||
"@types/lodash": "^4.14.151",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/supertest": "^6.0.0",
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# @backstage-community/plugin-azure-devops-common
|
||||
|
||||
## 0.14.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 8367480: Backstage version bump to v1.42.3
|
||||
|
||||
## 0.13.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@backstage-community/plugin-azure-devops-common",
|
||||
"version": "0.13.0",
|
||||
"version": "0.14.0",
|
||||
"backstage": {
|
||||
"role": "common-library",
|
||||
"pluginId": "azure-devops",
|
||||
|
@ -41,11 +41,11 @@
|
|||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^1.7.5",
|
||||
"@backstage/plugin-catalog-common": "^1.1.5",
|
||||
"@backstage/plugin-permission-common": "^0.9.1"
|
||||
"@backstage/catalog-model": "backstage:^",
|
||||
"@backstage/plugin-catalog-common": "backstage:^",
|
||||
"@backstage/plugin-permission-common": "backstage:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.33.1"
|
||||
"@backstage/cli": "backstage:^"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,16 @@
|
|||
# @backstage-community/plugin-azure-devops
|
||||
|
||||
## 0.19.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 8367480: Backstage version bump to v1.42.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [8367480]
|
||||
- @backstage-community/plugin-azure-devops-common@0.14.0
|
||||
|
||||
## 0.18.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
@ -412,3 +412,43 @@ async handle(
|
|||
};
|
||||
}
|
||||
```
|
||||
|
||||
## New Frontend System (Alpha)
|
||||
|
||||
The Azure DevOps plugin currently support the New Frontend System via an `/alpha` export, here's how to use it:
|
||||
|
||||
### Use new frontend system
|
||||
|
||||
1. Install the frontend plugin:
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
yarn --cwd packages/app add @backstage-community/plugin-azure-devops
|
||||
```
|
||||
|
||||
2. Enable the plugin in your `packages/app(-next)/src/App.tsx`:
|
||||
|
||||
After all other imports:
|
||||
|
||||
```tsx
|
||||
import azureDevOpsPlugin from '@backstage-community/plugin-azure-devops';
|
||||
```
|
||||
|
||||
```tsx
|
||||
export const app = createApp({
|
||||
features: [
|
||||
catalogPlugin,
|
||||
catalogImportPlugin,
|
||||
userSettingsPlugin,
|
||||
azureDevOpsPlugin,
|
||||
// ...
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
Alternatively you can simply use feature discover and skip the above step by adding the following yo your `app-config.yaml` file:
|
||||
|
||||
```yaml
|
||||
app:
|
||||
packages: all
|
||||
```
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@backstage-community/plugin-azure-devops",
|
||||
"version": "0.18.0",
|
||||
"version": "0.19.0",
|
||||
"backstage": {
|
||||
"role": "frontend-plugin",
|
||||
"pluginId": "azure-devops",
|
||||
|
@ -52,14 +52,14 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@backstage-community/plugin-azure-devops-common": "workspace:^",
|
||||
"@backstage/catalog-model": "^1.7.5",
|
||||
"@backstage/core-compat-api": "^0.4.4",
|
||||
"@backstage/core-components": "^0.17.4",
|
||||
"@backstage/core-plugin-api": "^1.10.9",
|
||||
"@backstage/errors": "^1.2.7",
|
||||
"@backstage/frontend-plugin-api": "^0.10.4",
|
||||
"@backstage/plugin-catalog-react": "^1.19.1",
|
||||
"@backstage/plugin-permission-react": "^0.4.36",
|
||||
"@backstage/catalog-model": "backstage:^",
|
||||
"@backstage/core-compat-api": "backstage:^",
|
||||
"@backstage/core-components": "backstage:^",
|
||||
"@backstage/core-plugin-api": "backstage:^",
|
||||
"@backstage/errors": "backstage:^",
|
||||
"@backstage/frontend-plugin-api": "backstage:^",
|
||||
"@backstage/plugin-catalog-react": "backstage:^",
|
||||
"@backstage/plugin-permission-react": "backstage:^",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
|
@ -68,9 +68,9 @@
|
|||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.33.1",
|
||||
"@backstage/dev-utils": "^1.1.12",
|
||||
"@backstage/test-utils": "^1.7.10",
|
||||
"@backstage/cli": "backstage:^",
|
||||
"@backstage/dev-utils": "backstage:^",
|
||||
"@backstage/test-utils": "backstage:^",
|
||||
"@testing-library/dom": "^10.0.0",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/react": "^15.0.0",
|
||||
|
|
|
@ -7,17 +7,19 @@
|
|||
|
||||
import { AnyApiFactory } from '@backstage/core-plugin-api';
|
||||
import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
|
||||
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { ApiFactory } from '@backstage/core-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityCardType } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionDefinition } from '@backstage/frontend-plugin-api';
|
||||
import { FrontendPlugin } from '@backstage/frontend-plugin-api';
|
||||
import { JSX as JSX_2 } from 'react';
|
||||
import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api';
|
||||
import { RouteRef } from '@backstage/frontend-plugin-api';
|
||||
|
||||
// @alpha (undocumented)
|
||||
const _default: FrontendPlugin<
|
||||
const _default: OverridableFrontendPlugin<
|
||||
{},
|
||||
{},
|
||||
{
|
||||
|
@ -26,15 +28,17 @@ const _default: FrontendPlugin<
|
|||
name: undefined;
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
|
||||
inputs: {};
|
||||
params: {
|
||||
factory: AnyApiFactory;
|
||||
};
|
||||
params: <
|
||||
TApi,
|
||||
TImpl extends TApi,
|
||||
TDeps extends {
|
||||
[x: string]: unknown;
|
||||
},
|
||||
>(
|
||||
params: ApiFactory<TApi, TImpl, TDeps>,
|
||||
) => ExtensionBlueprintParams<AnyApiFactory>;
|
||||
}>;
|
||||
'entity-card:azure-devops/readme': ExtensionDefinition<{
|
||||
kind: 'entity-card';
|
||||
|
@ -48,22 +52,22 @@ const _default: FrontendPlugin<
|
|||
type?: 'content' | 'summary' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
|
||||
| ExtensionDataRef<
|
||||
(entity: Entity) => boolean,
|
||||
'catalog.entity-filter-function',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<
|
||||
string,
|
||||
'catalog.entity-filter-expression',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<
|
||||
EntityCardType,
|
||||
'catalog.entity-card-type',
|
||||
{
|
||||
|
@ -93,35 +97,31 @@ const _default: FrontendPlugin<
|
|||
group?: string | false | undefined;
|
||||
};
|
||||
output:
|
||||
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
|
||||
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
|
||||
| ExtensionDataRef<string, 'core.routing.path', {}>
|
||||
| ExtensionDataRef<
|
||||
RouteRef<AnyRouteRefParams>,
|
||||
'core.routing.ref',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<
|
||||
string,
|
||||
'catalog.entity-content-title',
|
||||
{}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<
|
||||
(entity: Entity) => boolean,
|
||||
'catalog.entity-filter-function',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<
|
||||
string,
|
||||
'catalog.entity-filter-expression',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<string, 'catalog.entity-content-title', {}>
|
||||
| ExtensionDataRef<
|
||||
string,
|
||||
'catalog.entity-content-group',
|
||||
{
|
||||
|
@ -130,10 +130,12 @@ const _default: FrontendPlugin<
|
|||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
defaultPath: string;
|
||||
defaultTitle: string;
|
||||
defaultGroup?:
|
||||
defaultPath?: [Error: "Use the 'path' param instead"] | undefined;
|
||||
path: string;
|
||||
defaultTitle?: [Error: "Use the 'title' param instead"] | undefined;
|
||||
title: string;
|
||||
defaultGroup?: [Error: "Use the 'group' param instead"] | undefined;
|
||||
group?:
|
||||
| (string & {})
|
||||
| 'development'
|
||||
| 'deployment'
|
||||
|
@ -142,6 +144,7 @@ const _default: FrontendPlugin<
|
|||
| 'operation'
|
||||
| 'observability'
|
||||
| undefined;
|
||||
loader: () => Promise<JSX.Element>;
|
||||
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
|
||||
filter?: EntityPredicate | ((entity: Entity) => boolean) | undefined;
|
||||
};
|
||||
|
@ -162,35 +165,31 @@ const _default: FrontendPlugin<
|
|||
group?: string | false | undefined;
|
||||
};
|
||||
output:
|
||||
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
|
||||
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
|
||||
| ExtensionDataRef<string, 'core.routing.path', {}>
|
||||
| ExtensionDataRef<
|
||||
RouteRef<AnyRouteRefParams>,
|
||||
'core.routing.ref',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<
|
||||
string,
|
||||
'catalog.entity-content-title',
|
||||
{}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<
|
||||
(entity: Entity) => boolean,
|
||||
'catalog.entity-filter-function',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<
|
||||
string,
|
||||
'catalog.entity-filter-expression',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<string, 'catalog.entity-content-title', {}>
|
||||
| ExtensionDataRef<
|
||||
string,
|
||||
'catalog.entity-content-group',
|
||||
{
|
||||
|
@ -199,10 +198,12 @@ const _default: FrontendPlugin<
|
|||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
defaultPath: string;
|
||||
defaultTitle: string;
|
||||
defaultGroup?:
|
||||
defaultPath?: [Error: "Use the 'path' param instead"] | undefined;
|
||||
path: string;
|
||||
defaultTitle?: [Error: "Use the 'title' param instead"] | undefined;
|
||||
title: string;
|
||||
defaultGroup?: [Error: "Use the 'group' param instead"] | undefined;
|
||||
group?:
|
||||
| (string & {})
|
||||
| 'development'
|
||||
| 'deployment'
|
||||
|
@ -211,6 +212,7 @@ const _default: FrontendPlugin<
|
|||
| 'operation'
|
||||
| 'observability'
|
||||
| undefined;
|
||||
loader: () => Promise<JSX.Element>;
|
||||
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
|
||||
filter?: EntityPredicate | ((entity: Entity) => boolean) | undefined;
|
||||
};
|
||||
|
@ -231,35 +233,31 @@ const _default: FrontendPlugin<
|
|||
group?: string | false | undefined;
|
||||
};
|
||||
output:
|
||||
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
|
||||
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
|
||||
| ExtensionDataRef<string, 'core.routing.path', {}>
|
||||
| ExtensionDataRef<
|
||||
RouteRef<AnyRouteRefParams>,
|
||||
'core.routing.ref',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<
|
||||
string,
|
||||
'catalog.entity-content-title',
|
||||
{}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<
|
||||
(entity: Entity) => boolean,
|
||||
'catalog.entity-filter-function',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<
|
||||
string,
|
||||
'catalog.entity-filter-expression',
|
||||
{
|
||||
optional: true;
|
||||
}
|
||||
>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<string, 'catalog.entity-content-title', {}>
|
||||
| ExtensionDataRef<
|
||||
string,
|
||||
'catalog.entity-content-group',
|
||||
{
|
||||
|
@ -268,10 +266,12 @@ const _default: FrontendPlugin<
|
|||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
defaultPath: string;
|
||||
defaultTitle: string;
|
||||
defaultGroup?:
|
||||
defaultPath?: [Error: "Use the 'path' param instead"] | undefined;
|
||||
path: string;
|
||||
defaultTitle?: [Error: "Use the 'title' param instead"] | undefined;
|
||||
title: string;
|
||||
defaultGroup?: [Error: "Use the 'group' param instead"] | undefined;
|
||||
group?:
|
||||
| (string & {})
|
||||
| 'development'
|
||||
| 'deployment'
|
||||
|
@ -280,6 +280,7 @@ const _default: FrontendPlugin<
|
|||
| 'operation'
|
||||
| 'observability'
|
||||
| undefined;
|
||||
loader: () => Promise<JSX.Element>;
|
||||
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
|
||||
filter?: EntityPredicate | ((entity: Entity) => boolean) | undefined;
|
||||
};
|
||||
|
@ -294,9 +295,9 @@ const _default: FrontendPlugin<
|
|||
path?: string | undefined;
|
||||
};
|
||||
output:
|
||||
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
|
||||
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
|
||||
| ConfigurableExtensionDataRef<
|
||||
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
|
||||
| ExtensionDataRef<string, 'core.routing.path', {}>
|
||||
| ExtensionDataRef<
|
||||
RouteRef<AnyRouteRefParams>,
|
||||
'core.routing.ref',
|
||||
{
|
||||
|
@ -305,7 +306,8 @@ const _default: FrontendPlugin<
|
|||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
defaultPath: string;
|
||||
defaultPath?: [Error: "Use the 'path' param instead"] | undefined;
|
||||
path: string;
|
||||
loader: () => Promise<JSX.Element>;
|
||||
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
|
||||
};
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
import {
|
||||
ApiBlueprint,
|
||||
createApiFactory,
|
||||
PageBlueprint,
|
||||
createFrontendPlugin,
|
||||
discoveryApiRef,
|
||||
|
@ -36,8 +35,8 @@ import { isAzureDevOpsAvailable, isAzurePipelinesAvailable } from '../plugin';
|
|||
|
||||
/** @alpha */
|
||||
export const azureDevOpsApi = ApiBlueprint.make({
|
||||
params: {
|
||||
factory: createApiFactory({
|
||||
params: defineParams =>
|
||||
defineParams({
|
||||
api: azureDevOpsApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
|
@ -46,13 +45,12 @@ export const azureDevOpsApi = ApiBlueprint.make({
|
|||
factory: ({ discoveryApi, fetchApi }) =>
|
||||
new AzureDevOpsClient({ discoveryApi, fetchApi }),
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
/** @alpha */
|
||||
export const azureDevOpsPullRequestPage = PageBlueprint.make({
|
||||
params: {
|
||||
defaultPath: '/azure-pull-requests',
|
||||
path: '/azure-pull-requests',
|
||||
routeRef: convertLegacyRouteRef(azurePullRequestDashboardRouteRef),
|
||||
loader: () =>
|
||||
import('../components/PullRequestsPage').then(m =>
|
||||
|
@ -65,8 +63,8 @@ export const azureDevOpsPullRequestPage = PageBlueprint.make({
|
|||
export const azureDevOpsPipelinesEntityContent = EntityContentBlueprint.make({
|
||||
name: 'pipelines',
|
||||
params: {
|
||||
defaultPath: '/pipelines',
|
||||
defaultTitle: 'Pipelines',
|
||||
path: '/pipelines',
|
||||
title: 'Pipelines',
|
||||
filter: isAzurePipelinesAvailable,
|
||||
loader: () =>
|
||||
import('../components/EntityPageAzurePipelines').then(m =>
|
||||
|
@ -79,8 +77,8 @@ export const azureDevOpsPipelinesEntityContent = EntityContentBlueprint.make({
|
|||
export const azureDevOpsGitTagsEntityContent = EntityContentBlueprint.make({
|
||||
name: 'git-tags',
|
||||
params: {
|
||||
defaultPath: '/git-tags',
|
||||
defaultTitle: 'Git Tags',
|
||||
path: '/git-tags',
|
||||
title: 'Git Tags',
|
||||
filter: isAzureDevOpsAvailable,
|
||||
loader: () =>
|
||||
import('../components/EntityPageAzureGitTags').then(m =>
|
||||
|
@ -94,8 +92,8 @@ export const azureDevOpsPullRequestsEntityContent = EntityContentBlueprint.make(
|
|||
{
|
||||
name: 'pull-requests',
|
||||
params: {
|
||||
defaultPath: '/pull-requests',
|
||||
defaultTitle: 'Pull Requests',
|
||||
path: '/pull-requests',
|
||||
title: 'Pull Requests',
|
||||
filter: isAzureDevOpsAvailable,
|
||||
loader: () =>
|
||||
import('../components/EntityPageAzurePullRequests').then(m =>
|
||||
|
|
|
@ -1,5 +1,16 @@
|
|||
# @backstage-community/plugin-catalog-backend-module-azure-devops-annotator-processor
|
||||
|
||||
## 0.11.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 8367480: Backstage version bump to v1.42.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [8367480]
|
||||
- @backstage-community/plugin-azure-devops-common@0.14.0
|
||||
|
||||
## 0.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@backstage-community/plugin-catalog-backend-module-azure-devops-annotator-processor",
|
||||
"description": "The azure-devops-annotator-processor backend module for the catalog plugin.",
|
||||
"version": "0.10.0",
|
||||
"version": "0.11.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
@ -31,17 +31,17 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@backstage-community/plugin-azure-devops-common": "workspace:^",
|
||||
"@backstage/backend-plugin-api": "^1.4.1",
|
||||
"@backstage/catalog-model": "^1.7.5",
|
||||
"@backstage/config": "^1.3.3",
|
||||
"@backstage/integration": "^1.17.1",
|
||||
"@backstage/plugin-catalog-common": "^1.1.5",
|
||||
"@backstage/plugin-catalog-node": "^1.17.2",
|
||||
"@backstage/backend-plugin-api": "backstage:^",
|
||||
"@backstage/catalog-model": "backstage:^",
|
||||
"@backstage/config": "backstage:^",
|
||||
"@backstage/integration": "backstage:^",
|
||||
"@backstage/plugin-catalog-common": "backstage:^",
|
||||
"@backstage/plugin-catalog-node": "backstage:^",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "^1.7.0",
|
||||
"@backstage/cli": "^0.33.1"
|
||||
"@backstage/backend-test-utils": "backstage:^",
|
||||
"@backstage/cli": "backstage:^"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# @backstage-community/plugin-scaffolder-backend-module-azure-devops
|
||||
|
||||
## 0.13.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 8367480: Backstage version bump to v1.42.3
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@backstage-community/plugin-scaffolder-backend-module-azure-devops",
|
||||
"description": "The azure-devops module for @backstage/plugin-scaffolder-backend",
|
||||
"version": "0.12.0",
|
||||
"version": "0.13.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
@ -39,17 +39,17 @@
|
|||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-plugin-api": "^1.4.1",
|
||||
"@backstage/config": "^1.3.3",
|
||||
"@backstage/errors": "^1.2.7",
|
||||
"@backstage/integration": "^1.17.1",
|
||||
"@backstage/plugin-scaffolder-node": "^0.10.0",
|
||||
"@backstage/backend-plugin-api": "backstage:^",
|
||||
"@backstage/config": "backstage:^",
|
||||
"@backstage/errors": "backstage:^",
|
||||
"@backstage/integration": "backstage:^",
|
||||
"@backstage/plugin-scaffolder-node": "backstage:^",
|
||||
"azure-devops-node-api": "^14.1.0",
|
||||
"yaml": "^2.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.33.1",
|
||||
"@backstage/plugin-scaffolder-node-test-utils": "^0.3.1"
|
||||
"@backstage/cli": "backstage:^",
|
||||
"@backstage/plugin-scaffolder-node-test-utils": "backstage:^"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# @backstage-community/plugin-scaffolder-backend-module-dotnet
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 8367480: Backstage version bump to v1.42.3
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@backstage-community/plugin-scaffolder-backend-module-dotnet",
|
||||
"description": "The azure-devops module for @backstage/plugin-scaffolder-backend",
|
||||
|
@ -39,15 +39,15 @@
|
|||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-plugin-api": "^1.4.1",
|
||||
"@backstage/plugin-scaffolder-node": "^0.10.0",
|
||||
"@backstage/backend-plugin-api": "backstage:^",
|
||||
"@backstage/plugin-scaffolder-node": "backstage:^",
|
||||
"fs-extra": "^11.3.0",
|
||||
"yaml": "^2.6.0",
|
||||
"zod": "^3.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.33.1",
|
||||
"@backstage/plugin-scaffolder-node-test-utils": "^0.3.1",
|
||||
"@backstage/cli": "backstage:^",
|
||||
"@backstage/plugin-scaffolder-node-test-utils": "backstage:^",
|
||||
"@types/fs-extra": "^11"
|
||||
},
|
||||
"files": [
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'@backstage-community/plugin-azure-storage-explorer': minor
|
||||
'@backstage-community/plugin-azure-storage-explorer-backend': minor
|
||||
---
|
||||
|
||||
Backstage version bump to v1.41.1
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"version": "1.40.2"
|
||||
"version": "1.41.1"
|
||||
}
|
||||
|
|
|
@ -36,9 +36,9 @@
|
|||
"directory": "workspaces/azure-storage-explorer"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.33.0",
|
||||
"@backstage/cli": "^0.33.1",
|
||||
"@backstage/e2e-test-utils": "^0.1.1",
|
||||
"@backstage/repo-tools": "^0.14.0",
|
||||
"@backstage/repo-tools": "^0.15.0",
|
||||
"@changesets/cli": "^2.27.1",
|
||||
"knip": "^5.27.4",
|
||||
"node-gyp": "^9.0.0",
|
||||
|
|
|
@ -20,29 +20,29 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@backstage-community/plugin-azure-storage-explorer": "workspace:^",
|
||||
"@backstage/app-defaults": "^1.6.3",
|
||||
"@backstage/catalog-model": "^1.7.4",
|
||||
"@backstage/cli": "^0.33.0",
|
||||
"@backstage/core-app-api": "^1.17.1",
|
||||
"@backstage/core-components": "^0.17.3",
|
||||
"@backstage/core-plugin-api": "^1.10.8",
|
||||
"@backstage/integration-react": "^1.2.8",
|
||||
"@backstage/plugin-api-docs": "^0.12.8",
|
||||
"@backstage/plugin-catalog": "^1.31.0",
|
||||
"@backstage/plugin-catalog-common": "^1.1.4",
|
||||
"@backstage/plugin-catalog-graph": "^0.4.20",
|
||||
"@backstage/plugin-catalog-import": "^0.13.2",
|
||||
"@backstage/plugin-catalog-react": "^1.19.0",
|
||||
"@backstage/plugin-org": "^0.6.40",
|
||||
"@backstage/plugin-permission-react": "^0.4.35",
|
||||
"@backstage/plugin-scaffolder": "^1.32.0",
|
||||
"@backstage/plugin-search": "^1.4.27",
|
||||
"@backstage/plugin-search-react": "^1.9.1",
|
||||
"@backstage/plugin-techdocs": "^1.13.1",
|
||||
"@backstage/plugin-techdocs-module-addons-contrib": "^1.1.25",
|
||||
"@backstage/plugin-techdocs-react": "^1.3.0",
|
||||
"@backstage/plugin-user-settings": "^0.8.23",
|
||||
"@backstage/theme": "^0.6.6",
|
||||
"@backstage/app-defaults": "^1.6.4",
|
||||
"@backstage/catalog-model": "^1.7.5",
|
||||
"@backstage/cli": "^0.33.1",
|
||||
"@backstage/core-app-api": "^1.18.0",
|
||||
"@backstage/core-components": "^0.17.4",
|
||||
"@backstage/core-plugin-api": "^1.10.9",
|
||||
"@backstage/integration-react": "^1.2.9",
|
||||
"@backstage/plugin-api-docs": "^0.12.9",
|
||||
"@backstage/plugin-catalog": "^1.31.1",
|
||||
"@backstage/plugin-catalog-common": "^1.1.5",
|
||||
"@backstage/plugin-catalog-graph": "^0.4.21",
|
||||
"@backstage/plugin-catalog-import": "^0.13.3",
|
||||
"@backstage/plugin-catalog-react": "^1.19.1",
|
||||
"@backstage/plugin-org": "^0.6.41",
|
||||
"@backstage/plugin-permission-react": "^0.4.36",
|
||||
"@backstage/plugin-scaffolder": "^1.33.0",
|
||||
"@backstage/plugin-search": "^1.4.28",
|
||||
"@backstage/plugin-search-react": "^1.9.2",
|
||||
"@backstage/plugin-techdocs": "^1.13.2",
|
||||
"@backstage/plugin-techdocs-module-addons-contrib": "^1.1.26",
|
||||
"@backstage/plugin-techdocs-react": "^1.3.1",
|
||||
"@backstage/plugin-user-settings": "^0.8.24",
|
||||
"@backstage/theme": "^0.6.7",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"react": "^18.0.2",
|
||||
|
@ -51,7 +51,7 @@
|
|||
"react-router-dom": "^6.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/test-utils": "^1.7.9",
|
||||
"@backstage/test-utils": "^1.7.10",
|
||||
"@playwright/test": "^1.32.3",
|
||||
"@testing-library/dom": "^9.0.0",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
|
|
|
@ -22,27 +22,27 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@backstage-community/plugin-azure-storage-explorer-backend": "workspace:^",
|
||||
"@backstage/backend-defaults": "^0.11.0",
|
||||
"@backstage/catalog-client": "^1.10.1",
|
||||
"@backstage/config": "^1.3.2",
|
||||
"@backstage/plugin-app-backend": "^0.5.3",
|
||||
"@backstage/plugin-auth-backend": "^0.25.1",
|
||||
"@backstage/plugin-auth-backend-module-github-provider": "^0.3.4",
|
||||
"@backstage/plugin-auth-backend-module-guest-provider": "^0.2.9",
|
||||
"@backstage/plugin-auth-node": "^0.6.4",
|
||||
"@backstage/plugin-catalog-backend": "^2.1.0",
|
||||
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.2.9",
|
||||
"@backstage/plugin-permission-backend": "^0.7.1",
|
||||
"@backstage/plugin-permission-backend-module-allow-all-policy": "^0.2.9",
|
||||
"@backstage/plugin-permission-common": "^0.9.0",
|
||||
"@backstage/plugin-permission-node": "^0.10.1",
|
||||
"@backstage/plugin-proxy-backend": "^0.6.3",
|
||||
"@backstage/plugin-scaffolder-backend": "^2.0.0",
|
||||
"@backstage/plugin-search-backend": "^2.0.3",
|
||||
"@backstage/plugin-search-backend-module-catalog": "^0.3.5",
|
||||
"@backstage/plugin-search-backend-module-techdocs": "^0.4.3",
|
||||
"@backstage/plugin-search-backend-node": "^1.3.12",
|
||||
"@backstage/plugin-techdocs-backend": "^2.0.3",
|
||||
"@backstage/backend-defaults": "^0.11.1",
|
||||
"@backstage/catalog-client": "^1.10.2",
|
||||
"@backstage/config": "^1.3.3",
|
||||
"@backstage/plugin-app-backend": "^0.5.4",
|
||||
"@backstage/plugin-auth-backend": "^0.25.2",
|
||||
"@backstage/plugin-auth-backend-module-github-provider": "^0.3.5",
|
||||
"@backstage/plugin-auth-backend-module-guest-provider": "^0.2.10",
|
||||
"@backstage/plugin-auth-node": "^0.6.5",
|
||||
"@backstage/plugin-catalog-backend": "^3.0.0",
|
||||
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.2.10",
|
||||
"@backstage/plugin-permission-backend": "^0.7.2",
|
||||
"@backstage/plugin-permission-backend-module-allow-all-policy": "^0.2.10",
|
||||
"@backstage/plugin-permission-common": "^0.9.1",
|
||||
"@backstage/plugin-permission-node": "^0.10.2",
|
||||
"@backstage/plugin-proxy-backend": "^0.6.4",
|
||||
"@backstage/plugin-scaffolder-backend": "^2.1.0",
|
||||
"@backstage/plugin-search-backend": "^2.0.4",
|
||||
"@backstage/plugin-search-backend-module-catalog": "^0.3.6",
|
||||
"@backstage/plugin-search-backend-module-techdocs": "^0.4.4",
|
||||
"@backstage/plugin-search-backend-node": "^1.3.13",
|
||||
"@backstage/plugin-techdocs-backend": "^2.0.4",
|
||||
"app": "link:../app",
|
||||
"better-sqlite3": "^9.0.0",
|
||||
"dockerode": "^3.3.1",
|
||||
|
@ -52,7 +52,7 @@
|
|||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.33.0"
|
||||
"@backstage/cli": "^0.33.1"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
|
|
@ -40,19 +40,19 @@
|
|||
"dependencies": {
|
||||
"@azure/identity": "4.10.2",
|
||||
"@azure/storage-blob": "12.27.0",
|
||||
"@backstage/backend-defaults": "^0.11.0",
|
||||
"@backstage/backend-plugin-api": "^1.4.0",
|
||||
"@backstage/config": "^1.3.2",
|
||||
"@backstage/backend-defaults": "^0.11.1",
|
||||
"@backstage/backend-plugin-api": "^1.4.1",
|
||||
"@backstage/config": "^1.3.3",
|
||||
"@backstage/errors": "^1.2.7",
|
||||
"@types/express": "*",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "^1.6.0",
|
||||
"@backstage/cli": "^0.33.0",
|
||||
"@backstage/plugin-auth-backend": "^0.25.1",
|
||||
"@backstage/plugin-auth-backend-module-guest-provider": "^0.2.9",
|
||||
"@backstage/backend-test-utils": "^1.7.0",
|
||||
"@backstage/cli": "^0.33.1",
|
||||
"@backstage/plugin-auth-backend": "^0.25.2",
|
||||
"@backstage/plugin-auth-backend-module-guest-provider": "^0.2.10",
|
||||
"@types/supertest": "^6.0.0",
|
||||
"supertest": "^7.0.0"
|
||||
},
|
||||
|
|
|
@ -39,10 +39,10 @@
|
|||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-components": "^0.17.3",
|
||||
"@backstage/core-plugin-api": "^1.10.8",
|
||||
"@backstage/core-components": "^0.17.4",
|
||||
"@backstage/core-plugin-api": "^1.10.9",
|
||||
"@backstage/errors": "^1.2.7",
|
||||
"@backstage/theme": "^0.6.6",
|
||||
"@backstage/theme": "^0.6.7",
|
||||
"@material-ui/core": "^4.9.13",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "^4.0.0-alpha.61",
|
||||
|
@ -54,10 +54,10 @@
|
|||
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.33.0",
|
||||
"@backstage/core-app-api": "^1.17.1",
|
||||
"@backstage/dev-utils": "^1.1.11",
|
||||
"@backstage/test-utils": "^1.7.9",
|
||||
"@backstage/cli": "^0.33.1",
|
||||
"@backstage/core-app-api": "^1.18.0",
|
||||
"@backstage/dev-utils": "^1.1.12",
|
||||
"@backstage/test-utils": "^1.7.10",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,8 @@
|
|||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "restricted",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": []
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
.git
|
||||
.yarn/cache
|
||||
.yarn/install-state.gz
|
||||
node_modules
|
||||
packages/*/src
|
||||
packages/*/node_modules
|
||||
plugins
|
||||
*.local.yaml
|
|
@ -0,0 +1 @@
|
|||
playwright.config.ts
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
};
|
|
@ -0,0 +1,56 @@
|
|||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Coverage directory generated when running tests with coverage
|
||||
coverage
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Yarn files
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# Node version directives
|
||||
.nvmrc
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# Build output
|
||||
dist
|
||||
dist-types
|
||||
|
||||
# Temporary change files created by Vim
|
||||
*.swp
|
||||
|
||||
# MkDocs build output
|
||||
site
|
||||
|
||||
# Local configuration files
|
||||
*.local.yaml
|
||||
|
||||
# Sensitive credentials
|
||||
*-credentials.yaml
|
||||
|
||||
# vscode database functionality support files
|
||||
*.session.sql
|
||||
|
||||
# E2E test reports
|
||||
e2e-test-report/
|
||||
|
||||
# Cache
|
||||
.cache/
|
|
@ -0,0 +1,6 @@
|
|||
.vscode
|
||||
coverage
|
||||
dist
|
||||
dist-types
|
||||
knip-report.md
|
||||
report.api.md
|
|
@ -0,0 +1,13 @@
|
|||
# [Backstage](https://backstage.io)
|
||||
|
||||
> [!NOTE]
|
||||
> See the [plugin README](./plugins/bookmarks/README.md) for more information about the Bookmarks plugin.
|
||||
|
||||
This is your newly scaffolded Backstage App, Good Luck!
|
||||
|
||||
To start the app, run:
|
||||
|
||||
```sh
|
||||
yarn install
|
||||
yarn start
|
||||
```
|
|
@ -0,0 +1,64 @@
|
|||
app:
|
||||
title: Scaffolded Backstage App
|
||||
baseUrl: http://localhost:3000
|
||||
|
||||
backend:
|
||||
baseUrl: http://localhost:7007
|
||||
listen:
|
||||
port: 7007
|
||||
# Uncomment the following host directive to bind to specific interfaces
|
||||
# host: 127.0.0.1
|
||||
csp:
|
||||
connect-src: ["'self'", 'http:', 'https:']
|
||||
# Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference
|
||||
# Default Helmet Content-Security-Policy values can be removed by setting the key to false
|
||||
cors:
|
||||
origin: http://localhost:3000
|
||||
methods: [GET, HEAD, PATCH, POST, PUT, DELETE]
|
||||
credentials: true
|
||||
# This is for local development only, it is not recommended to use this in production
|
||||
# The production database configuration is stored in app-config.production.yaml
|
||||
database:
|
||||
client: better-sqlite3
|
||||
connection: ':memory:'
|
||||
# workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir
|
||||
|
||||
auth:
|
||||
# see https://backstage.io/docs/auth/ to learn about auth providers
|
||||
providers:
|
||||
# See https://backstage.io/docs/auth/guest/provider
|
||||
guest: {}
|
||||
|
||||
catalog:
|
||||
import:
|
||||
entityFilename: catalog-info.yaml
|
||||
pullRequestBranchName: backstage-integration
|
||||
rules:
|
||||
- allow: [Component, System, API, Resource, Location]
|
||||
locations:
|
||||
# Local example data, file locations are relative to the backend process, typically `packages/backend`
|
||||
- type: file
|
||||
target: ../../examples/entities.yaml
|
||||
|
||||
# Local example organizational data
|
||||
- type: file
|
||||
target: ../../examples/org.yaml
|
||||
rules:
|
||||
- allow: [User, Group]
|
||||
|
||||
- type: file
|
||||
target: ./plugins/bookmarks/examples/component/catalog-info.yaml
|
||||
## Uncomment these lines to add more example data
|
||||
# - type: url
|
||||
# target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml
|
||||
|
||||
## Uncomment these lines to add an example org
|
||||
# - type: url
|
||||
# target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml
|
||||
# rules:
|
||||
# - allow: [User, Group]
|
||||
|
||||
# see https://backstage.io/docs/permissions/getting-started for more on the permission framework
|
||||
permission:
|
||||
# setting this to `false` will disable permissions
|
||||
enabled: false
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"version": "1.41.0"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"knip-reports": true
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: bookmarks-test-app
|
||||
description: An example of a Backstage application.
|
||||
# Example for optional annotations
|
||||
# annotations:
|
||||
# github.com/project-slug: backstage/backstage
|
||||
# backstage.io/techdocs-ref: dir:.
|
||||
spec:
|
||||
type: website
|
||||
owner: john@example.com
|
||||
lifecycle: experimental
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"name": "@internal/bookmarks",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "20 || 22"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli repo start",
|
||||
"build:backend": "yarn workspace backend build",
|
||||
"build:all": "backstage-cli repo build --all",
|
||||
"build:api-reports": "yarn build:api-reports:only",
|
||||
"build:api-reports:only": "backstage-repo-tools api-reports -o ae-wrong-input-file-type,ae-undocumented --validate-release-tags",
|
||||
"build:knip-reports": "backstage-repo-tools knip-reports",
|
||||
"tsc": "tsc",
|
||||
"tsc:full": "tsc --skipLibCheck false --incremental false",
|
||||
"clean": "backstage-cli repo clean",
|
||||
"test": "backstage-cli repo test",
|
||||
"test:all": "backstage-cli repo test --coverage",
|
||||
"fix": "backstage-cli repo fix",
|
||||
"lint": "backstage-cli repo lint --since origin/master",
|
||||
"lint:all": "backstage-cli repo lint",
|
||||
"prettier:check": "prettier --check .",
|
||||
"prettier:fix": "prettier --write .",
|
||||
"new": "backstage-cli new"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*",
|
||||
"plugins/*"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.33.1",
|
||||
"@backstage/e2e-test-utils": "^0.1.1",
|
||||
"@backstage/repo-tools": "^0.15.0",
|
||||
"@changesets/cli": "^2.29.5",
|
||||
"node-gyp": "^11.3.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "~5.9.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18"
|
||||
},
|
||||
"prettier": "@backstage/cli/config/prettier",
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx,mjs,cjs}": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{json,md}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"jest": {
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"branches": 100,
|
||||
"functions": 100,
|
||||
"lines": 100,
|
||||
"statements": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
# The Plugins Folder
|
||||
|
||||
> [!NOTE]
|
||||
> See the [plugin README](./bookmarks/README.md) for more information about the Bookmarks plugin.
|
||||
|
||||
This is where your own plugins and their associated modules live, each in a
|
||||
separate folder of its own.
|
||||
|
||||
If you want to create a new plugin here, go to your project root directory, run
|
||||
the command `yarn new`, and follow the on-screen instructions.
|
||||
|
||||
You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)!
|
|
@ -0,0 +1 @@
|
|||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
|
@ -0,0 +1,7 @@
|
|||
# @backstage-community/plugin-bookmarks
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 80b21b2: Initial release
|
|
@ -0,0 +1,58 @@
|
|||
# Bookmarks plugin
|
||||
|
||||
The Bookmarks plugin is a simple tool for saving and viewing links to your favorite websites, Google Docs, and other online resources directly within Backstage.
|
||||
|
||||
Bookmarks are stored in the `metadata` of a Backstage entity, making it easy to manage and access them within your Backstage Software Catalog.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
To install the Bookmarks plugin, follow these steps:
|
||||
|
||||
1. Install `@backstage-community/plugin-bookmarks` to your frontend packages
|
||||
|
||||
```bash
|
||||
yarn --cwd packages/app add @backstage-community/plugin-bookmarks
|
||||
```
|
||||
|
||||
2. Add `EntityBookmarksContent` to the `EntityPage` routes:
|
||||
|
||||
```diff
|
||||
// In your packages/app/src/components/EntityPage.tsx
|
||||
import { EntityBookmarksContent, isBookmarksAvailable } from '@backstage-community/plugin-bookmarks';
|
||||
|
||||
// add to defaultEntityPage, etc. to see them in the other entity pages
|
||||
const serviceEntityPage = (
|
||||
<EntityLayout>
|
||||
{/* other routes */}
|
||||
+ <EntityLayout.Route path="/bookmarks" title="Bookmarks" if={isBookmarksAvailable}>
|
||||
+ <EntityBookmarksContent />
|
||||
+ </EntityLayout.Route>
|
||||
</EntityLayout>
|
||||
);
|
||||
```
|
||||
|
||||
3. Add bookmarks to your entities by including them in the `metadata` section of the entity YAML file:
|
||||
|
||||
```yaml
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
spec: # spec fields here...
|
||||
metadata:
|
||||
name: my-component
|
||||
bookmarks:
|
||||
'Cool link': https://example.com/cool-link
|
||||
```
|
||||
|
||||
4. Done! Enjoy your bookmarks by visiting the updated entity page in Backstage through your company catalog.
|
||||
|
||||
## Usage
|
||||
|
||||
Once installed, you can view bookmarks by navigating to the "Bookmarks" tab in the entity page of your Backstage application.
|
||||
|
||||
Note that only certain pages can be bookmarked. Due to cross-origin policy, you can only bookmark pages that allow embedding in an iframe. This means that some websites may not be viewable directly within Backstage.
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright 2025 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 { Header, Page, TabbedLayout } from '@backstage/core-components';
|
||||
import { UrlTree } from '../../src/types';
|
||||
import { StrictMode } from 'react';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
import { EntityBookmarksContent } from '../../src/components/EntityBookmarksContent/EntityBookmarksContent';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
const testData: UrlTree = {
|
||||
'Life story':
|
||||
'https://docs.google.com/document/d/1qaLicIa3FZKyup4JXo9ivNgWDmkbX6-XBaQNfKeKjpw/mobilebasic',
|
||||
'My cool gadgets and gizmos': {
|
||||
'fortune cowsay lolcat': 'https://logonoff.co/projects/fcl/index.html',
|
||||
'XP tour': 'https://logonoff.co/projects/windowsxptour/index.html',
|
||||
notepad: 'https://notepad.logonoff.co',
|
||||
},
|
||||
'Important documents': {
|
||||
'Team sync notes':
|
||||
'https://docs.google.com/document/d/1qaLicIa3FZKyup4JXo9ivNgWDmkbX6-XBaQNfKeKjpw/mobilebasic',
|
||||
Manifesto: {
|
||||
'Agile manifesto':
|
||||
'https://docs.google.com/document/d/1qaLicIa3FZKyup4JXo9ivNgWDmkbX6-XBaQNfKeKjpw/mobilebasic',
|
||||
'Scrum manifesto':
|
||||
'https://docs.google.com/document/d/1qaLicIa3FZKyup4JXo9ivNgWDmkbX6-XBaQNfKeKjpw/mobilebasic',
|
||||
},
|
||||
'Sprint planning':
|
||||
'https://docs.google.com/document/d/1qaLicIa3FZKyup4JXo9ivNgWDmkbX6-XBaQNfKeKjpw/mobilebasic',
|
||||
},
|
||||
};
|
||||
|
||||
const testEntity: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: { bookmarks: testData, name: 'my-service' },
|
||||
};
|
||||
|
||||
export const PluginTestPage = () => (
|
||||
<Page themeId="tool">
|
||||
<Header type="component — service" title="Bookmark plugin demo" />
|
||||
<TabbedLayout>
|
||||
<TabbedLayout.Route path="/" title="Bookmarks">
|
||||
<EntityProvider entity={testEntity}>
|
||||
<StrictMode>
|
||||
<EntityBookmarksContent />
|
||||
</StrictMode>
|
||||
</EntityProvider>
|
||||
</TabbedLayout.Route>
|
||||
</TabbedLayout>
|
||||
</Page>
|
||||
);
|
|
@ -0,0 +1,16 @@
|
|||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { bookmarksPlugin } from '../src/plugin';
|
||||
import { PluginTestPage } from './PluginTestPage/PluginTestPage';
|
||||
import { bookmarksTranslations } from '../src';
|
||||
import { AVAILABLE_LANGUAGES } from '../src/translations/translations';
|
||||
|
||||
createDevApp()
|
||||
.registerPlugin(bookmarksPlugin)
|
||||
.addTranslationResource(bookmarksTranslations)
|
||||
.setAvailableLanguages(AVAILABLE_LANGUAGES)
|
||||
.addPage({
|
||||
element: <PluginTestPage />,
|
||||
title: 'Root Page',
|
||||
path: '/bookmarks',
|
||||
})
|
||||
.render();
|
|
@ -0,0 +1,23 @@
|
|||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: example-website
|
||||
|
||||
bookmarks:
|
||||
'Life story': https://docs.google.com/document/d/1qaLicIa3FZKyup4JXo9ivNgWDmkbX6-XBaQNfKeKjpw/mobilebasic
|
||||
'My cool gadgets and gizmos':
|
||||
'fortune cowsay lolcat': https://logonoff.co/projects/fcl/index.html
|
||||
'XP tour': https://logonoff.co/projects/windowsxptour/index.html
|
||||
'notepad': https://notepad.logonoff.co
|
||||
'Important documents':
|
||||
'Team sync notes': https://docs.google.com/document/d/1qaLicIa3FZKyup4JXo9ivNgWDmkbX6-XBaQNfKeKjpw/mobilebasic
|
||||
'Manifesto':
|
||||
'Agile manifesto': https://docs.google.com/document/d/1qaLicIa3FZKyup4JXo9ivNgWDmkbX6-XBaQNfKeKjpw/mobilebasic
|
||||
'Scrum manifesto': https://docs.google.com/document/d/1qaLicIa3FZKyup4JXo9ivNgWDmkbX6-XBaQNfKeKjpw/mobilebasic
|
||||
'Sprint planning': https://docs.google.com/document/d/1qaLicIa3FZKyup4JXo9ivNgWDmkbX6-XBaQNfKeKjpw/mobilebasic
|
||||
spec:
|
||||
type: website
|
||||
lifecycle: experimental
|
||||
owner: guests
|
||||
system: examples
|
||||
providesApis: [example-grpc-api]
|
|
@ -0,0 +1,2 @@
|
|||
# Knip report
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"name": "@backstage-community/plugin-bookmarks",
|
||||
"version": "0.2.0",
|
||||
"license": "Apache-2.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "frontend-plugin",
|
||||
"pluginId": "bookmarks",
|
||||
"pluginPackages": [
|
||||
"@backstage-community/plugin-bookmarks"
|
||||
]
|
||||
},
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"prepublish": "node replace-style-injection-paths.js",
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"clean": "backstage-cli package clean",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^1.7.5",
|
||||
"@backstage/core-components": "^0.17.4",
|
||||
"@backstage/core-plugin-api": "^1.10.9",
|
||||
"@backstage/plugin-catalog-react": "^1.19.1",
|
||||
"@backstage/theme": "^0.6.7",
|
||||
"@mui/icons-material": "5.18.0",
|
||||
"@mui/material": "^5.18.0",
|
||||
"@mui/system": "5.18.0",
|
||||
"@mui/x-tree-view": "8.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.33.1",
|
||||
"@backstage/dev-utils": "^1.1.12",
|
||||
"@backstage/test-utils": "^1.7.10",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.7.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"react-router-dom": "~6.27.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/community-plugins",
|
||||
"directory": "workspaces/bookmarks/plugins/bookmarks"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage",
|
||||
"plugin"
|
||||
],
|
||||
"bugs": "https://github.com/backstage/community-plugins/issues",
|
||||
"maintainers": [
|
||||
"@logonoff"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
## API Report File for "@backstage-community/plugin-bookmarks"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import type { Entity } from '@backstage/catalog-model';
|
||||
import { JSX as JSX_2 } from 'react/jsx-runtime';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { TranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { TranslationResource } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
// @public
|
||||
export const AVAILABLE_LANGUAGES: string[];
|
||||
|
||||
// @public
|
||||
export const bookmarksPlugin: BackstagePlugin< {
|
||||
entityContent: RouteRef<undefined>;
|
||||
}, {}, {}>;
|
||||
|
||||
// @public
|
||||
export const bookmarksTranslationRef: TranslationRef<"bookmarks", {
|
||||
readonly "bookmarkViewer.newTab": "Open in new tab";
|
||||
readonly "bookmarkViewer.navButton.next": "Next";
|
||||
readonly "bookmarkViewer.navButton.previous": "Previous";
|
||||
readonly "bookmarkViewer.mobileView.toc": "Table of Contents";
|
||||
readonly "bookmarkViewerFrame.devModeWarning": "You may have to reload the page for the iframe to load correctly in development mode";
|
||||
readonly "entityBookmarksContent.invalid.title": "Invalid bookmarks format";
|
||||
readonly "entityBookmarksContent.invalid.description": "Ensure your bookmarks are structured correctly.";
|
||||
readonly "entityBookmarksContent.notFound.title": "No bookmarks found";
|
||||
readonly "entityBookmarksContent.notFound.description": "Add bookmarks to your entity's metadata to see them here.";
|
||||
}>;
|
||||
|
||||
// @public
|
||||
export const bookmarksTranslations: TranslationResource<"bookmarks">;
|
||||
|
||||
// @public
|
||||
export const EntityBookmarksContent: () => JSX_2.Element;
|
||||
|
||||
// @public
|
||||
export const isBookmarksAvailable: (entity: Entity) => entity is Entity & {
|
||||
metadata: {
|
||||
bookmarks?: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
```
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* Copyright 2025 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 { renderInTestApp } from '@backstage/test-utils';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { BookmarksViewer } from './BookmarksViewer';
|
||||
import { TEST_IDS } from '../../consts/testids';
|
||||
import { useIsDesktop } from '../../hooks/useIsDesktop';
|
||||
import { act } from 'react';
|
||||
|
||||
jest.mock('../../hooks/useIsDesktop', () => ({
|
||||
useIsDesktop: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockUseIsDesktop = useIsDesktop as jest.Mock;
|
||||
|
||||
const simpleTree = {
|
||||
foo: {
|
||||
bar: 'https://example.com/bar',
|
||||
baz: 'https://example.com/baz',
|
||||
},
|
||||
};
|
||||
|
||||
const complexTree = {
|
||||
foo: {
|
||||
bar: {
|
||||
baz: 'https://example.com/baz',
|
||||
qux: 'https://example.com/qux',
|
||||
},
|
||||
quux1: 'https://example.com/quux1',
|
||||
quux2: 'https://example.com/quux2',
|
||||
quux3: 'https://example.com/quux3',
|
||||
},
|
||||
quuz: {
|
||||
corge: 'https://example.com/corge',
|
||||
grault: 'https://example.com/grault',
|
||||
garply: 'https://example.com/garply',
|
||||
},
|
||||
};
|
||||
|
||||
const flattenedComplexTree = [
|
||||
{ label: 'baz', href: 'https://example.com/baz' },
|
||||
{ label: 'qux', href: 'https://example.com/qux' },
|
||||
{ label: 'quux1', href: 'https://example.com/quux1' },
|
||||
{ label: 'quux2', href: 'https://example.com/quux2' },
|
||||
{ label: 'quux3', href: 'https://example.com/quux3' },
|
||||
{ label: 'corge', href: 'https://example.com/corge' },
|
||||
{ label: 'grault', href: 'https://example.com/grault' },
|
||||
{ label: 'garply', href: 'https://example.com/garply' },
|
||||
];
|
||||
|
||||
describe('BookmarksViewer', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders viewer, TOC, and navigation buttons', async () => {
|
||||
mockUseIsDesktop.mockReturnValue(true);
|
||||
|
||||
await renderInTestApp(<BookmarksViewer tree={simpleTree} />);
|
||||
expect(
|
||||
screen.getByTestId(TEST_IDS.BookmarkViewerFrame.iframe),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId(TEST_IDS.TableOfContents.wrapper),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId(TEST_IDS.NavButton.previous),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId(TEST_IDS.NavButton.next)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId(TEST_IDS.BookmarksViewer.newTab),
|
||||
).toBeInTheDocument();
|
||||
|
||||
// when clicking the next button, the previous button should be enabled
|
||||
act(() => {
|
||||
screen.getByTestId(TEST_IDS.NavButton.next).click();
|
||||
});
|
||||
expect(screen.getByTestId(TEST_IDS.NavButton.previous)).toBeEnabled();
|
||||
expect(
|
||||
screen.queryByTestId(TEST_IDS.NavButton.next),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders open in new tab button with correct href', async () => {
|
||||
await renderInTestApp(<BookmarksViewer tree={simpleTree} />);
|
||||
const openTabButton = screen.getByTestId(TEST_IDS.BookmarksViewer.newTab);
|
||||
expect(openTabButton).toHaveAttribute('href', 'https://example.com/bar');
|
||||
expect(openTabButton).toHaveAttribute('target', '_blank');
|
||||
});
|
||||
|
||||
it('has correct next and previous labels', async () => {
|
||||
mockUseIsDesktop.mockReturnValue(true);
|
||||
|
||||
await renderInTestApp(<BookmarksViewer tree={complexTree} />);
|
||||
|
||||
const iframe = screen.queryByTestId(TEST_IDS.BookmarkViewerFrame.iframe);
|
||||
const tableOfContents = screen.getByTestId(
|
||||
TEST_IDS.TableOfContents.wrapper,
|
||||
);
|
||||
|
||||
/* forwards */
|
||||
// i = 0
|
||||
expect(
|
||||
screen.queryByTestId(TEST_IDS.NavButton.previous),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId(TEST_IDS.NavButton.next)).toHaveTextContent(
|
||||
flattenedComplexTree[1].label,
|
||||
);
|
||||
expect(tableOfContents).toHaveTextContent(flattenedComplexTree[1].label);
|
||||
expect(iframe).toHaveAttribute('src', flattenedComplexTree[0].href);
|
||||
|
||||
act(() => {
|
||||
screen.getByTestId(TEST_IDS.NavButton.next).click(); // i ++
|
||||
});
|
||||
|
||||
// i = 1 to i = last - 1
|
||||
for (let i = 1; i < flattenedComplexTree.length - 1; i++) {
|
||||
expect(tableOfContents).toHaveTextContent(flattenedComplexTree[i].label);
|
||||
expect(screen.getByTestId(TEST_IDS.NavButton.previous)).toBeEnabled();
|
||||
expect(screen.getByTestId(TEST_IDS.NavButton.next)).toHaveTextContent(
|
||||
flattenedComplexTree[i + 1].label,
|
||||
);
|
||||
expect(iframe).toHaveAttribute('src', flattenedComplexTree[i].href);
|
||||
|
||||
act(() => {
|
||||
screen.getByTestId(TEST_IDS.NavButton.next).click(); // i ++
|
||||
});
|
||||
}
|
||||
|
||||
// i = last
|
||||
expect(screen.getByTestId(TEST_IDS.NavButton.previous)).toBeEnabled();
|
||||
expect(
|
||||
screen.queryByTestId(TEST_IDS.NavButton.next),
|
||||
).not.toBeInTheDocument();
|
||||
expect(tableOfContents).toHaveTextContent(
|
||||
flattenedComplexTree[flattenedComplexTree.length - 1].label,
|
||||
);
|
||||
expect(iframe).toHaveAttribute(
|
||||
'src',
|
||||
flattenedComplexTree[flattenedComplexTree.length - 1].href,
|
||||
);
|
||||
|
||||
/* backwards */
|
||||
// i = last
|
||||
act(() => {
|
||||
screen.getByTestId(TEST_IDS.NavButton.previous).click(); // i --
|
||||
});
|
||||
|
||||
// i = last - 1 to i = 1
|
||||
for (let i = flattenedComplexTree.length - 2; i > 0; i--) {
|
||||
expect(screen.getByTestId(TEST_IDS.NavButton.previous)).toBeEnabled();
|
||||
expect(screen.getByTestId(TEST_IDS.NavButton.next)).toHaveTextContent(
|
||||
flattenedComplexTree[i + 1].label,
|
||||
);
|
||||
expect(tableOfContents).toHaveTextContent(flattenedComplexTree[i].label);
|
||||
expect(iframe).toHaveAttribute('src', flattenedComplexTree[i].href);
|
||||
|
||||
act(() => {
|
||||
screen.getByTestId(TEST_IDS.NavButton.previous).click(); // i --
|
||||
});
|
||||
}
|
||||
|
||||
// i = 0
|
||||
expect(
|
||||
screen.queryByTestId(TEST_IDS.NavButton.previous),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId(TEST_IDS.NavButton.next)).toHaveTextContent(
|
||||
flattenedComplexTree[1].label,
|
||||
);
|
||||
expect(tableOfContents).toHaveTextContent(flattenedComplexTree[0].label);
|
||||
expect(iframe).toHaveAttribute('src', flattenedComplexTree[0].href);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright 2025 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 ArrowForwardIcon from '@mui/icons-material/ArrowForward';
|
||||
import Button from '@mui/material/Button';
|
||||
import { ReactNode, memo, useMemo, useState } from 'react';
|
||||
import { UrlTree } from '../../types';
|
||||
import { TEST_IDS } from '../../consts/testids';
|
||||
import { FlattenedNode, useFlattenTree } from '../../hooks/useFlattenTree';
|
||||
import { useIsDesktop } from '../../hooks/useIsDesktop';
|
||||
import { useTranslation } from '../../hooks/useTranslation';
|
||||
import { BookmarkDesktopView } from './helpers/BookmarkDesktopView';
|
||||
import { BookmarkMobileView } from './helpers/BookmarkMobileView';
|
||||
import { BookmarkViewerFrame } from './helpers/BookmarkViewerFrame';
|
||||
import { NavButton } from './helpers/NavButton';
|
||||
import { TableOfContents } from './helpers/TableOfContents';
|
||||
|
||||
/** Props for layout components */
|
||||
export type BookmarkViewerLayoutProps = {
|
||||
toc: ReactNode;
|
||||
previousButton: ReactNode;
|
||||
viewer: ReactNode;
|
||||
openInNewTab: ReactNode;
|
||||
nextButton: ReactNode;
|
||||
};
|
||||
|
||||
export const BookmarksViewer = memo(({ tree }: { tree: UrlTree }) => {
|
||||
const flattenedTree = useFlattenTree(tree);
|
||||
const [currentNode, setCurrentNode] = useState<FlattenedNode>(
|
||||
flattenedTree[0],
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isDesktop = useIsDesktop();
|
||||
const View = isDesktop ? BookmarkDesktopView : BookmarkMobileView;
|
||||
|
||||
const currentFlattenedIndex = useMemo(() => {
|
||||
return flattenedTree.findIndex(url => url.key === currentNode.key);
|
||||
}, [flattenedTree, currentNode]);
|
||||
|
||||
const previousButton = useMemo(() => {
|
||||
const previousUrl = flattenedTree[currentFlattenedIndex - 1];
|
||||
|
||||
return previousUrl ? (
|
||||
<NavButton
|
||||
direction="previous"
|
||||
treeKey={previousUrl.key}
|
||||
onClick={() => {
|
||||
setCurrentNode(previousUrl);
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
}, [flattenedTree, setCurrentNode, currentFlattenedIndex]);
|
||||
|
||||
const nextButton = useMemo(() => {
|
||||
const nextUrl = flattenedTree[currentFlattenedIndex + 1];
|
||||
|
||||
return nextUrl ? (
|
||||
<NavButton
|
||||
direction="next"
|
||||
treeKey={nextUrl.key}
|
||||
onClick={() => {
|
||||
setCurrentNode(nextUrl);
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
}, [flattenedTree, setCurrentNode, currentFlattenedIndex]);
|
||||
|
||||
const viewer = <BookmarkViewerFrame src={currentNode.value} />;
|
||||
|
||||
const toc = (
|
||||
<TableOfContents
|
||||
tree={tree}
|
||||
currentNode={currentNode}
|
||||
setCurrentNode={setCurrentNode}
|
||||
/>
|
||||
);
|
||||
|
||||
const openInNewTab = (
|
||||
<Button
|
||||
href={currentNode.value}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
sx={{ mb: 2 }}
|
||||
endIcon={<ArrowForwardIcon />}
|
||||
data-testid={TEST_IDS.BookmarksViewer.newTab}
|
||||
>
|
||||
{t('bookmarkViewer.newTab')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
toc={toc}
|
||||
previousButton={previousButton}
|
||||
viewer={viewer}
|
||||
openInNewTab={openInNewTab}
|
||||
nextButton={nextButton}
|
||||
/>
|
||||
);
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2025 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 { renderInTestApp } from '@backstage/test-utils';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { BookmarkDesktopView } from './BookmarkDesktopView';
|
||||
import { TEST_IDS } from '../../../consts/testids';
|
||||
|
||||
describe('BookmarkDesktopView', () => {
|
||||
it('should render the bookmark desktop view with all the required elements', async () => {
|
||||
await renderInTestApp(
|
||||
<BookmarkDesktopView
|
||||
toc={<div data-testid="toc" />}
|
||||
openInNewTab={<div data-testid="openInNewTab" />}
|
||||
viewer={<div data-testid="viewer" />}
|
||||
nextButton={<div data-testid="nextButton" />}
|
||||
previousButton={<div data-testid="previousButton" />}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByTestId(TEST_IDS.BookmarkDesktopView.wrapper),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('toc')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('openInNewTab')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('viewer')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('nextButton')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('previousButton')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2025 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 Grid from '@mui/material/Grid';
|
||||
import Box from '@mui/material/Box';
|
||||
import type { BookmarkViewerLayoutProps } from '../BookmarksViewer';
|
||||
import { TEST_IDS } from '../../../consts/testids';
|
||||
|
||||
export const BookmarkDesktopView = ({
|
||||
toc,
|
||||
previousButton,
|
||||
viewer,
|
||||
openInNewTab,
|
||||
nextButton,
|
||||
}: BookmarkViewerLayoutProps) => (
|
||||
<Grid
|
||||
direction="row"
|
||||
container
|
||||
spacing={2}
|
||||
sx={{ height: '100%' }}
|
||||
data-testid={TEST_IDS.BookmarkDesktopView.wrapper}
|
||||
>
|
||||
<Grid item md={2} sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
{toc}
|
||||
{previousButton}
|
||||
</Grid>
|
||||
<Grid item md={8} sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
{viewer}
|
||||
</Grid>
|
||||
<Grid item md={2} sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
{openInNewTab}
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
{nextButton}
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright 2025 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 { renderInTestApp } from '@backstage/test-utils';
|
||||
import { act, screen, waitFor } from '@testing-library/react';
|
||||
import { BookmarkMobileView } from './BookmarkMobileView';
|
||||
import { TEST_IDS } from '../../../consts/testids';
|
||||
|
||||
describe('BookmarkMobileView', () => {
|
||||
it('should render the bookmark mobile view with all the required elements', async () => {
|
||||
await renderInTestApp(
|
||||
<BookmarkMobileView
|
||||
toc={<div data-testid="toc" />}
|
||||
openInNewTab={<div data-testid="openInNewTab" />}
|
||||
viewer={<div data-testid="viewer" />}
|
||||
nextButton={<div data-testid="nextButton" />}
|
||||
previousButton={<div data-testid="previousButton" />}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByTestId(TEST_IDS.BookmarkMobileView.wrapper),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId(TEST_IDS.BookmarkMobileView.toggleToc),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('openInNewTab')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('viewer')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('nextButton')).toBeInTheDocument();
|
||||
|
||||
// toc is hidden by default and expanded by clicking the toggle
|
||||
expect(screen.queryByTestId('toc')).not.toBeInTheDocument();
|
||||
|
||||
// we hide the previous button in mobile view
|
||||
expect(screen.queryByTestId('previousButton')).not.toBeInTheDocument();
|
||||
|
||||
// clicking the toc toggle should open the drawer
|
||||
act(() => {
|
||||
screen.getByTestId(TEST_IDS.BookmarkMobileView.toggleToc).click();
|
||||
});
|
||||
expect(screen.getByTestId('toc')).toBeInTheDocument();
|
||||
|
||||
// clicking the backdrop should close the drawer
|
||||
act(() => {
|
||||
screen.getByTestId(TEST_IDS.BookmarkMobileView.backdrop).click();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByTestId(TEST_IDS.BookmarkMobileView.backdrop),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByTestId('toc')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright 2025 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 TocIcon from '@mui/icons-material/Toc';
|
||||
import Box from '@mui/material/Box';
|
||||
import Drawer from '@mui/material/Drawer';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import { BookmarkViewerLayoutProps } from '../BookmarksViewer';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from '../../../hooks/useTranslation';
|
||||
import { TEST_IDS } from '../../../consts/testids';
|
||||
|
||||
export const BookmarkMobileView = ({
|
||||
toc,
|
||||
openInNewTab,
|
||||
nextButton,
|
||||
viewer,
|
||||
}: BookmarkViewerLayoutProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [tocDrawerOpen, setTocDrawerOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}
|
||||
data-testid={TEST_IDS.BookmarkMobileView.wrapper}
|
||||
>
|
||||
<Drawer
|
||||
anchor="left"
|
||||
open={tocDrawerOpen}
|
||||
onClose={() => setTocDrawerOpen(false)}
|
||||
ModalProps={{
|
||||
slotProps: {
|
||||
backdrop: {
|
||||
'data-testid': TEST_IDS.BookmarkMobileView.backdrop,
|
||||
} as React.HTMLAttributes<HTMLDivElement>,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box sx={{ minWidth: 250, padding: 2 }}>{toc}</Box>
|
||||
</Drawer>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
pb: 2,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row', gap: 1 }}>
|
||||
<Tooltip title={t('bookmarkViewer.mobileView.toc')}>
|
||||
<IconButton
|
||||
onClick={() => setTocDrawerOpen(prev => !prev)}
|
||||
sx={{ mb: 2 }}
|
||||
data-testid={TEST_IDS.BookmarkMobileView.toggleToc}
|
||||
>
|
||||
<TocIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{openInNewTab}
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row', gap: 1 }}>
|
||||
{nextButton}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{ flexGrow: 1, width: '100%', minHeight: '50vh', height: '100%' }}
|
||||
>
|
||||
{viewer}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2025 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 { renderInTestApp } from '@backstage/test-utils';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { BookmarkViewerFrame } from './BookmarkViewerFrame';
|
||||
import { TEST_IDS } from '../../../consts/testids';
|
||||
import { useIsIframeLoading } from '../../../hooks/useIsIframeLoading';
|
||||
|
||||
jest.mock('../../../hooks/useIsIframeLoading', () => ({
|
||||
useIsIframeLoading: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockUseIsIframeLoading = useIsIframeLoading as jest.Mock;
|
||||
|
||||
describe('BookmarkViewerFrame', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('shows loading spinner when iframe is loading', async () => {
|
||||
process.env.NODE_ENV = 'production';
|
||||
mockUseIsIframeLoading.mockReturnValue(true);
|
||||
await renderInTestApp(<BookmarkViewerFrame src="https://example.com" />);
|
||||
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
||||
const iframe = screen.getByTestId(TEST_IDS.BookmarkViewerFrame.iframe);
|
||||
expect(iframe).toHaveStyle('visibility: hidden');
|
||||
expect(iframe).toHaveAttribute('src', 'https://example.com');
|
||||
expect(
|
||||
screen.queryByText('bookmarkViewerFrame.devModeWarning'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows iframe when not loading', async () => {
|
||||
mockUseIsIframeLoading.mockReturnValue(false);
|
||||
await renderInTestApp(<BookmarkViewerFrame src="https://example.com" />);
|
||||
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
|
||||
const iframe = screen.getByTestId(TEST_IDS.BookmarkViewerFrame.iframe);
|
||||
expect(iframe).toHaveStyle('visibility: visible');
|
||||
expect(iframe).toHaveAttribute('src', 'https://example.com');
|
||||
});
|
||||
|
||||
it('shows dev mode warning in development', async () => {
|
||||
process.env.NODE_ENV = 'development';
|
||||
mockUseIsIframeLoading.mockReturnValue(true);
|
||||
await renderInTestApp(<BookmarkViewerFrame src="https://example.com" />);
|
||||
expect(
|
||||
screen.getByText('bookmarkViewerFrame.devModeWarning'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright 2025 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 Box from '@mui/material/Box';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import { memo, useRef } from 'react';
|
||||
import { useIsIframeLoading } from '../../../hooks/useIsIframeLoading';
|
||||
import { TEST_IDS } from '../../../consts/testids';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { useTranslation } from '../../../hooks/useTranslation';
|
||||
|
||||
export const BookmarkViewerFrame = memo(({ src }: { src: string }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
const isIframeLoading = useIsIframeLoading(iframeRef, src);
|
||||
|
||||
/**
|
||||
* In development mode, React's Fast Refresh (live reload) can remount components,
|
||||
* causing this hook to reset its loading state. This leads the iframe to incorrectly
|
||||
* appearing as loading.
|
||||
*/
|
||||
const isDevEnv = process.env.NODE_ENV === 'development';
|
||||
|
||||
return (
|
||||
<>
|
||||
{isIframeLoading && (
|
||||
<Box
|
||||
sx={{
|
||||
mt: 5,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<CircularProgress />
|
||||
|
||||
{isDevEnv && (
|
||||
<Typography variant="body2" sx={{ mt: 2 }}>
|
||||
{t('bookmarkViewerFrame.devModeWarning')}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
component="iframe"
|
||||
data-testid={TEST_IDS.BookmarkViewerFrame.iframe}
|
||||
ref={iframeRef}
|
||||
referrerPolicy="no-referrer"
|
||||
src={src}
|
||||
// disallow top-navigation, top-navigation-by-user-activation, popups-to-escape-sandbox
|
||||
sandbox="allow-scripts allow-same-origin allow-popups allow-forms allow-presentation allow-modals allow-orientation-lock allow-pointer-lock"
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
border: 'none',
|
||||
background: 'white',
|
||||
visibility: isIframeLoading ? 'hidden' : 'visible',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2025 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 { renderInTestApp } from '@backstage/test-utils';
|
||||
import { screen, fireEvent } from '@testing-library/react';
|
||||
import { NavButton } from './NavButton';
|
||||
import { PATH_SEPARATOR } from '../../../consts/consts';
|
||||
|
||||
const TREE_KEY = ['foo', 'bar'].join(PATH_SEPARATOR);
|
||||
|
||||
describe('NavButton', () => {
|
||||
it('renders next button with correct treeKey', async () => {
|
||||
const handleClick = jest.fn();
|
||||
await renderInTestApp(
|
||||
<NavButton direction="next" onClick={handleClick} treeKey={TREE_KEY} />,
|
||||
);
|
||||
expect(
|
||||
screen.getByText('bookmarkViewer.navButton.next'),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('bar')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders previous button with correct treeKey', async () => {
|
||||
const handleClick = jest.fn();
|
||||
await renderInTestApp(
|
||||
<NavButton
|
||||
direction="previous"
|
||||
onClick={handleClick}
|
||||
treeKey={TREE_KEY}
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
screen.getByText('bookmarkViewer.navButton.previous'),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('bar')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onClick when clicked', async () => {
|
||||
const handleClick = jest.fn();
|
||||
await renderInTestApp(
|
||||
<NavButton direction="next" onClick={handleClick} treeKey={TREE_KEY} />,
|
||||
);
|
||||
fireEvent.click(screen.getByRole('button'));
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright 2025 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 ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { PATH_SEPARATOR } from '../../../consts/consts';
|
||||
import { useTranslation } from '../../../hooks/useTranslation';
|
||||
import { TEST_IDS } from '../../../consts/testids';
|
||||
|
||||
/** Button component for navigating between bookmarks */
|
||||
export const NavButton = ({
|
||||
direction,
|
||||
onClick,
|
||||
treeKey,
|
||||
}: {
|
||||
direction: 'next' | 'previous';
|
||||
onClick: () => void;
|
||||
treeKey: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isNext = direction === 'next';
|
||||
const buttonText = t(`bookmarkViewer.navButton.${direction}`);
|
||||
/** last path item is the button label */
|
||||
const bookmarkName = treeKey.split(PATH_SEPARATOR).pop();
|
||||
|
||||
return (
|
||||
<Button
|
||||
aria-label={`${buttonText}: ${bookmarkName}`}
|
||||
color="inherit"
|
||||
data-testid={TEST_IDS.NavButton[direction]}
|
||||
onClick={onClick}
|
||||
startIcon={!isNext ? <ArrowBackIcon /> : undefined}
|
||||
endIcon={isNext ? <ArrowForwardIcon /> : undefined}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: isNext ? 'flex-end' : 'flex-start',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
textAlign: isNext ? 'right' : 'left',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
component="small"
|
||||
sx={{ color: theme => theme.palette.text.secondary }}
|
||||
variant="body2"
|
||||
>
|
||||
{buttonText}
|
||||
</Typography>
|
||||
<Typography component="span" variant="body1">
|
||||
{bookmarkName}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Button>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright 2025 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 { renderInTestApp } from '@backstage/test-utils';
|
||||
import { act, screen, waitFor } from '@testing-library/react';
|
||||
import { TableOfContents } from './TableOfContents';
|
||||
import { PATH_SEPARATOR } from '../../../consts/consts';
|
||||
import type { UrlTree } from '../../../types';
|
||||
import type { FlattenedNode } from '../../../hooks/useFlattenTree';
|
||||
|
||||
const tree: UrlTree = {
|
||||
Foo: {
|
||||
Bar: 'https://example.com/bar',
|
||||
Baz: 'https://example.com/baz',
|
||||
},
|
||||
Qux: 'https://example.com/qux',
|
||||
Goo: {
|
||||
SubGoo: 'https://example.com/sub-goo',
|
||||
},
|
||||
};
|
||||
|
||||
const currentNode: FlattenedNode = {
|
||||
key: ['Foo', 'Bar', 'https://example.com/bar'].join(PATH_SEPARATOR),
|
||||
value: 'https://example.com/bar',
|
||||
};
|
||||
|
||||
describe('TableOfContents', () => {
|
||||
it('renders all tree items', async () => {
|
||||
await renderInTestApp(
|
||||
<TableOfContents
|
||||
tree={tree}
|
||||
currentNode={currentNode}
|
||||
setCurrentNode={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText('Foo')).toBeInTheDocument();
|
||||
expect(screen.getByText('Bar')).toBeInTheDocument();
|
||||
expect(screen.getByText('Baz')).toBeInTheDocument();
|
||||
expect(screen.getByText('Qux')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('expands and collapses tree items', async () => {
|
||||
await renderInTestApp(
|
||||
<TableOfContents
|
||||
tree={tree}
|
||||
currentNode={currentNode}
|
||||
setCurrentNode={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
const gooItem = screen.getByText('Goo');
|
||||
|
||||
expect(screen.queryByText('SubGoo')).not.toBeInTheDocument();
|
||||
|
||||
act(() => {
|
||||
gooItem.click();
|
||||
});
|
||||
expect(screen.getByText('SubGoo')).toBeVisible();
|
||||
|
||||
act(() => {
|
||||
gooItem.click();
|
||||
});
|
||||
waitFor(() => {
|
||||
expect(screen.queryByText('SubGoo')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls setCurrentNode when an item is clicked', async () => {
|
||||
const setCurrentNodeMock = jest.fn();
|
||||
await renderInTestApp(
|
||||
<TableOfContents
|
||||
tree={tree}
|
||||
currentNode={currentNode}
|
||||
setCurrentNode={setCurrentNodeMock}
|
||||
/>,
|
||||
);
|
||||
const barItem = screen.getByText('Bar');
|
||||
act(() => {
|
||||
barItem.click();
|
||||
});
|
||||
expect(setCurrentNodeMock).toHaveBeenCalledWith({
|
||||
key: ['Foo', 'Bar'].join(PATH_SEPARATOR),
|
||||
value: 'https://example.com/bar',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright 2025 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 { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
|
||||
import { TreeItem } from '@mui/x-tree-view/TreeItem';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { UrlTree } from '../../../types';
|
||||
import { PATH_SEPARATOR } from '../../../consts/consts';
|
||||
import {
|
||||
FlattenedNode,
|
||||
useUnorderedFlattenedTree,
|
||||
} from '../../../hooks/useFlattenTree';
|
||||
import { TEST_IDS } from '../../../consts/testids';
|
||||
|
||||
/** Recursively render a portion of a UrlTree */
|
||||
const RecursiveTreeItem = ({
|
||||
treeKey,
|
||||
subTree,
|
||||
path = [],
|
||||
}: {
|
||||
treeKey: string;
|
||||
subTree: UrlTree;
|
||||
path?: string[];
|
||||
}) => {
|
||||
const value = subTree[treeKey];
|
||||
const currentPath = [...path, treeKey];
|
||||
const itemId = currentPath.join(PATH_SEPARATOR);
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return (
|
||||
<TreeItem
|
||||
itemId={itemId}
|
||||
label={treeKey}
|
||||
data-testid={TEST_IDS.TableOfContents.leaf}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// if the value is not a string this must be a subtree
|
||||
return (
|
||||
<TreeItem itemId={itemId} label={treeKey}>
|
||||
{Object.keys(value).map(subKey => (
|
||||
<RecursiveTreeItem
|
||||
key={subKey}
|
||||
treeKey={subKey}
|
||||
subTree={value}
|
||||
path={currentPath}
|
||||
/>
|
||||
))}
|
||||
</TreeItem>
|
||||
);
|
||||
};
|
||||
|
||||
/** Component to render a table of contents from a UrlTree */
|
||||
export const TableOfContents = ({
|
||||
tree,
|
||||
currentNode: { key: pathKey },
|
||||
setCurrentNode,
|
||||
}: {
|
||||
tree: UrlTree;
|
||||
currentNode: FlattenedNode;
|
||||
setCurrentNode: (url: FlattenedNode) => void;
|
||||
}) => {
|
||||
const [expandedItems, setExpandedItems] = useState<string[]>([]);
|
||||
|
||||
const urlLookup = useUnorderedFlattenedTree(tree);
|
||||
|
||||
// auto expand items when the current path changes
|
||||
useEffect(() => {
|
||||
const parts = pathKey.split(PATH_SEPARATOR);
|
||||
const parents = parts.map((_, i) =>
|
||||
parts.slice(0, i + 1).join(PATH_SEPARATOR),
|
||||
);
|
||||
setExpandedItems(prev => Array.from(new Set([...prev, ...parents])));
|
||||
}, [pathKey]);
|
||||
|
||||
return (
|
||||
<SimpleTreeView
|
||||
selectedItems={pathKey}
|
||||
onSelectedItemsChange={(_, itemId: string | null) => {
|
||||
if (!itemId || !urlLookup[itemId]) return;
|
||||
setCurrentNode({ value: urlLookup[itemId], key: itemId });
|
||||
}}
|
||||
expandedItems={expandedItems}
|
||||
onItemExpansionToggle={(_, itemId: string) => {
|
||||
setExpandedItems(prev =>
|
||||
prev.includes(itemId)
|
||||
? prev.filter(id => id !== itemId)
|
||||
: [...prev, itemId],
|
||||
);
|
||||
}}
|
||||
sx={{ flexGrow: 1 }}
|
||||
data-testid={TEST_IDS.TableOfContents.wrapper}
|
||||
>
|
||||
{Object.keys(tree).map(key => (
|
||||
<RecursiveTreeItem key={key} treeKey={key} subTree={tree} />
|
||||
))}
|
||||
</SimpleTreeView>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright 2025 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 { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { EntityBookmarksContent } from './EntityBookmarksContent';
|
||||
|
||||
jest.mock('@backstage/plugin-catalog-react', () => ({
|
||||
useEntity: jest.fn(),
|
||||
}));
|
||||
|
||||
const validBookmarks = { foo: { bar: 'https://example.com' } };
|
||||
const useEntityMock = useEntity as jest.Mock;
|
||||
|
||||
describe('EntityBookmarksContent', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('shows empty state when no bookmarks', async () => {
|
||||
useEntityMock.mockReturnValue({ entity: { metadata: {} } });
|
||||
await renderInTestApp(<EntityBookmarksContent />);
|
||||
expect(
|
||||
screen.getByText('entityBookmarksContent.notFound.title'),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('entityBookmarksContent.notFound.description'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows invalid format state when bookmarks are invalid', async () => {
|
||||
useEntityMock.mockReturnValue({
|
||||
entity: { metadata: { bookmarks: { foo: 123 } } },
|
||||
});
|
||||
await renderInTestApp(<EntityBookmarksContent />);
|
||||
expect(
|
||||
screen.getByText('entityBookmarksContent.invalid.title'),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('entityBookmarksContent.invalid.description'),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText('entityBookmarksContent.notFound.title'),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText('entityBookmarksContent.notFound.description'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders BookmarksViewer when bookmarks are valid', async () => {
|
||||
useEntityMock.mockReturnValue({
|
||||
entity: { metadata: { bookmarks: validBookmarks } },
|
||||
});
|
||||
await renderInTestApp(<EntityBookmarksContent />);
|
||||
|
||||
expect(
|
||||
screen.queryByText('entityBookmarksContent.invalid.title'),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText('entityBookmarksContent.notFound.title'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2025 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 { BookmarksViewer } from '../BookmarksViewer/BookmarksViewer';
|
||||
import { EmptyState } from '@backstage/core-components';
|
||||
import { useTranslation } from '../../hooks/useTranslation';
|
||||
import { USE_TREE_ERROR, useTree } from '../../hooks/useTree';
|
||||
|
||||
export const EntityBookmarksContent = () => {
|
||||
const { tree, error } = useTree();
|
||||
const { t } = useTranslation();
|
||||
|
||||
switch (error) {
|
||||
case USE_TREE_ERROR.INVALID:
|
||||
return (
|
||||
<EmptyState
|
||||
title={t('entityBookmarksContent.invalid.title')}
|
||||
description={t('entityBookmarksContent.invalid.description')}
|
||||
missing="data"
|
||||
/>
|
||||
);
|
||||
|
||||
case USE_TREE_ERROR.NOT_FOUND:
|
||||
return (
|
||||
<EmptyState
|
||||
title={t('entityBookmarksContent.notFound.title')}
|
||||
description={t('entityBookmarksContent.notFound.description')}
|
||||
missing="data"
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return <BookmarksViewer tree={tree} />;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2025 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Magic value used as a separator for path keys, that is not
|
||||
* valid in any URL and is unlikely to appear in folder names
|
||||
*/
|
||||
export const PATH_SEPARATOR = ':::PLUGIN-BOOKMARKS-SEPARATOR:::';
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2025 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.
|
||||
*/
|
||||
|
||||
/** Test selector IDs */
|
||||
export const TEST_IDS = {
|
||||
BookmarkDesktopView: {
|
||||
wrapper: 'plugin-bookmarks-view-desktop',
|
||||
},
|
||||
BookmarkMobileView: {
|
||||
backdrop: 'plugin-bookmarks-view-mobile-backdrop',
|
||||
toggleToc: 'plugin-bookmarks-view-mobile-toggle-toc',
|
||||
wrapper: 'plugin-bookmarks-view-mobile',
|
||||
},
|
||||
BookmarksViewer: {
|
||||
newTab: 'plugin-bookmarks-new-tab',
|
||||
},
|
||||
BookmarkViewerFrame: {
|
||||
iframe: 'plugin-bookmarks-iframe',
|
||||
},
|
||||
TableOfContents: {
|
||||
leaf: 'plugin-bookmarks-toc-leaf',
|
||||
wrapper: 'plugin-bookmarks-toc-wrapper',
|
||||
},
|
||||
NavButton: {
|
||||
next: 'plugin-bookmarks-nav-next',
|
||||
previous: 'plugin-bookmarks-nav-previous',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright 2025 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 { renderHook } from '@testing-library/react';
|
||||
import { useFlattenTree, useUnorderedFlattenedTree } from './useFlattenTree';
|
||||
import { PATH_SEPARATOR } from '../consts/consts';
|
||||
|
||||
describe('useFlattenTree', () => {
|
||||
it('should export hook', () => {
|
||||
expect(useFlattenTree).toBeDefined();
|
||||
});
|
||||
|
||||
it('should flatten a simple UrlTree', () => {
|
||||
const tree = {
|
||||
docs: 'https://docs.example.com',
|
||||
blog: 'https://blog.example.com',
|
||||
};
|
||||
const { result } = renderHook(() => useFlattenTree(tree));
|
||||
expect(result.current).toEqual([
|
||||
{
|
||||
key: ['docs'].join(PATH_SEPARATOR),
|
||||
value: 'https://docs.example.com',
|
||||
},
|
||||
{
|
||||
key: ['blog'].join(PATH_SEPARATOR),
|
||||
value: 'https://blog.example.com',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should flatten a nested UrlTree', () => {
|
||||
const tree = {
|
||||
docs: {
|
||||
gettingStarted: 'https://docs.example.com/getting-started',
|
||||
api: 'https://docs.example.com/api',
|
||||
},
|
||||
blog: 'https://blog.example.com',
|
||||
};
|
||||
const { result } = renderHook(() => useFlattenTree(tree));
|
||||
expect(result.current).toEqual([
|
||||
{
|
||||
key: ['docs', 'gettingStarted'].join(PATH_SEPARATOR),
|
||||
value: 'https://docs.example.com/getting-started',
|
||||
},
|
||||
{
|
||||
key: ['docs', 'api'].join(PATH_SEPARATOR),
|
||||
value: 'https://docs.example.com/api',
|
||||
},
|
||||
{
|
||||
key: ['blog'].join(PATH_SEPARATOR),
|
||||
value: 'https://blog.example.com',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return empty array for empty tree', () => {
|
||||
const tree = {};
|
||||
const { result } = renderHook(() => useFlattenTree(tree));
|
||||
expect(result.current).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle deeply nested UrlTree', () => {
|
||||
const tree = {
|
||||
a: {
|
||||
b: {
|
||||
c: 'url1',
|
||||
},
|
||||
d: 'url2',
|
||||
},
|
||||
e: 'url3',
|
||||
};
|
||||
const { result } = renderHook(() => useFlattenTree(tree));
|
||||
expect(result.current).toEqual([
|
||||
{ key: ['a', 'b', 'c'].join(PATH_SEPARATOR), value: 'url1' },
|
||||
{ key: ['a', 'd'].join(PATH_SEPARATOR), value: 'url2' },
|
||||
{ key: ['e'].join(PATH_SEPARATOR), value: 'url3' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useUnorderedFlattenedTree', () => {
|
||||
it('should convert flattened tree to key-value map', () => {
|
||||
const tree = {
|
||||
docs: {
|
||||
gettingStarted: 'https://docs.example.com/getting-started',
|
||||
api: 'https://docs.example.com/api',
|
||||
},
|
||||
blog: 'https://blog.example.com',
|
||||
};
|
||||
const { result } = renderHook(() => useUnorderedFlattenedTree(tree));
|
||||
|
||||
const gettingStartedKey = ['docs', 'gettingStarted'].join(PATH_SEPARATOR);
|
||||
const apiKey = ['docs', 'api'].join(PATH_SEPARATOR);
|
||||
const blogKey = ['blog'].join(PATH_SEPARATOR);
|
||||
|
||||
expect(result.current).toEqual({
|
||||
[gettingStartedKey]: 'https://docs.example.com/getting-started',
|
||||
[apiKey]: 'https://docs.example.com/api',
|
||||
[blogKey]: 'https://blog.example.com',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty object for empty tree', () => {
|
||||
const tree = {};
|
||||
const { result } = renderHook(() => useUnorderedFlattenedTree(tree));
|
||||
expect(result.current).toEqual({});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2025 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 { useMemo } from 'react';
|
||||
import { UrlTree } from '../types';
|
||||
import { PATH_SEPARATOR } from '../consts/consts';
|
||||
|
||||
/** Flattened node type with key and value */
|
||||
export type FlattenedNode = { key: string; value: string };
|
||||
|
||||
/** Flattened representation of a UrlTree */
|
||||
export type FlattenedTree = FlattenedNode[];
|
||||
|
||||
/** Flatten the UrlTree to an array of URLs, with key as full path */
|
||||
export const useFlattenTree = (tree: UrlTree): FlattenedTree =>
|
||||
useMemo(() => {
|
||||
const acc: FlattenedTree = [];
|
||||
const inOrderTraverse = (node: UrlTree, path: string[] = []) =>
|
||||
Object.entries(node).forEach(([key, value]) =>
|
||||
typeof value === 'string'
|
||||
? acc.push({ key: [...path, key].join(PATH_SEPARATOR), value })
|
||||
: inOrderTraverse(value, [...path, key]),
|
||||
);
|
||||
inOrderTraverse(tree);
|
||||
return acc;
|
||||
}, [tree]);
|
||||
|
||||
export type UnorderedFlattenedTree = { [key: string]: string };
|
||||
|
||||
/** Convert a UrlTree to a key-value map of flattened URLs for easy lookup */
|
||||
export const useUnorderedFlattenedTree = (
|
||||
tree: UrlTree,
|
||||
): UnorderedFlattenedTree => {
|
||||
const flatTree = useFlattenTree(tree);
|
||||
return useMemo(
|
||||
() => Object.fromEntries(flatTree.map(({ key, value }) => [key, value])),
|
||||
[flatTree],
|
||||
);
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2025 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 { renderHook } from '@testing-library/react';
|
||||
import { useIsDesktop } from './useIsDesktop';
|
||||
import { mockBreakpoint } from '@backstage/core-components/testUtils';
|
||||
|
||||
describe('useIsDesktop', () => {
|
||||
it('should export hook', () => {
|
||||
expect(useIsDesktop).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return true for desktop viewports', () => {
|
||||
mockBreakpoint({ matches: true });
|
||||
const { result } = renderHook(() => useIsDesktop());
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for mobile viewports', () => {
|
||||
mockBreakpoint({ matches: false });
|
||||
const { result } = renderHook(() => useIsDesktop());
|
||||
expect(result.current).toBe(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2025 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 { useTheme } from '@mui/material/styles';
|
||||
import { useMediaQuery } from '@mui/system';
|
||||
|
||||
/** A hook that determines if the current viewport is considered desktop-sized. */
|
||||
export const useIsDesktop = (): boolean => {
|
||||
const theme = useTheme();
|
||||
return useMediaQuery(theme.breakpoints.up('md'));
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue