Compare commits

..

No commits in common. "main" and "@backstage-community/plugin-newrelic-dashboard@0.13.0" have entirely different histories.

423 changed files with 25560 additions and 43160 deletions

5
.github/CODEOWNERS vendored
View File

@ -7,13 +7,11 @@
* @backstage/community-plugins-maintainers
yarn.lock @backstage/community-plugins-maintainers @backstage-service
*/yarn.lock @backstage/community-plugins-maintainers @backstage-service
.github/archived-plugins.json @backstage/community-plugins-maintainers
/workspaces/3scale @backstage/community-plugins-maintainers @04kash @AndrienkoAleksandr
/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
@ -32,7 +30,6 @@ 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
@ -81,7 +78,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 @logonoff
/workspaces/npm @backstage/community-plugins-maintainers @christoph-jerolimov @ciiay @karthikjeeyar
/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

View File

@ -33,7 +33,6 @@ body:
- bazaar
- bitrise
- blackduck
- bookmarks
- cicd-statistics
- cloudbuild
- code-climate

View File

@ -34,7 +34,6 @@ body:
- bazaar
- bitrise
- blackduck
- bookmarks
- cicd-statistics
- cloudbuild
- code-climate

View File

@ -24,7 +24,6 @@ attributes:
- bazaar
- bitrise
- blackduck
- bookmarks
- cicd-statistics
- cloudbuild
- code-climate

View File

@ -1,3 +0,0 @@
{
"archived": []
}

3
.github/labeler.yml vendored
View File

@ -58,9 +58,6 @@ workspace/bitrise:
workspace/blackduck:
- "Workspace\\s*blackduck"
workspace/bookmarks:
- "Workspace\\s*bookmarks"
workspace/cicd-statistics:
- "Workspace\\s*cicd-statistics"

View File

@ -1,19 +0,0 @@
name: Deprecate Archived Plugins
on:
push:
paths:
- '.github/archived-plugins.json'
branches:
- main
jobs:
deprecate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deprecate packages
run: ./scripts/ci/deprecate-archived-plugins.sh
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@ -78,13 +78,6 @@ 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"
@ -104,7 +97,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: |

View File

@ -1,28 +0,0 @@
# Archived Workspaces
This document contains a list of workspaces and plugins that have been archived from the community-plugins repository.
## About Archived Workspaces
When a workspace or plugin is archived:
- The code is no longer actively maintained
- The npm packages are deprecated with a deprecation message
- The workspace/plugin is removed from the active codebase
- A Git tag can provide historical reference to the source
- An entry is added to this document for reference
To archive a workspace or plugin, use the `archive-workspace` script:
```bash
# Archive an entire workspace
./scripts/archive-workspace workspace-name
# Archive a specific plugin within a workspace
./scripts/archive-workspace workspace-name plugin-name
```
## Archived Items
| Workspace | Package | Reason | Source |
| --------- | ------- | ------ | ------ |

View File

@ -8,9 +8,6 @@
- [PR Reviews \& Merging](#pr-reviews--merging)
- [Issue Triage](#issue-triage)
- [Stepping Down as a Plugin Owner](#stepping-down-as-a-plugin-owner)
- [Archiving a Plugin](#archiving-a-plugin)
- [When to Archive](#when-to-archive)
- [How to Archive](#how-to-archive)
- [Version Bumping](#version-bumping)
- [Opt-in to Automatic Version Bump PRs](#opt-in-to-automatic-version-bump-prs)
- [Opt-in to Knip Reports Check](#opt-in-to-knip-reports-check)
@ -50,59 +47,7 @@ If you are no longer maintaining a plugin, please take the following steps to fo
3. If you are the last remaining CODEOWNER:
- The `@backstage/community-plugins-maintainers` group will provide best-effort support for issues and maintenance. Without a dedicated owner, plugin updates and support may be slower or limited.
- To help with this, opt into automatic version bump PRs by creating an empty `.auto-version-bump` file in the plugin's workspace (i.e., `workspaces/${WORKSPACE}/.auto-version-bump`).
## Archiving a Plugin
When a plugin is no longer maintained, it should be archived rather than abandoned. The archival process ensures that users are properly notified through npm deprecation warnings while preserving the code for historical reference through Git tags.
### When to Archive
Consider archiving a plugin when:
- The plugin is no longer actively maintained
- No current maintainer is available (and no one is stepping up)
- The plugin has unresolved security vulnerabilities that won't be fixed
- The plugin functionality has been superseded by better alternatives
- The plugin is incompatible with current Backstage versions and won't be updated
### How to Archive
Follow these steps to archive a plugin or workspace:
1. Run the archive script to designate the plugin(s) as archived:
```bash
# Archive an entire workspace (defaults to "No longer maintained")
node scripts/archive.js workspace-name
# Archive an entire workspace with custom reason
node scripts/archive.js workspace-name "Custom reason"
# Archive a specific plugin within a workspace (use package name after @backstage-community/)
node scripts/archive.js workspace-name plugin-name "Custom reason"
```
This will:
- Record Git tag references using `package.json` versions (`@backstage-community/plugin-example@1.2.3`)
- Add entries to `.github/archived-plugins.json` and `ARCHIVED_WORKSPACES.md`
2. Dry run the following script to verify which packages would be deprecated:
```bash
./scripts/ci/deprecate-archived-plugins.sh --dry-run
```
3. Delete the workspace or plugin(s) from the repository.
4. Open a PR with the changes including:
- Updated `.github/archived-plugins.json`
- Updated `ARCHIVED_WORKSPACES.md`
- Removed workspace/plugin
5. Once the PR is merged, the GitHub Action will automatically deprecate the packages in `.github/archived-plugins.json` on npm. Note the `.github/archived-plugins.json` requires codeowner approval from `@backstage/community-plugins-maintainers`.
- To help with this, opt into automatic version bump PRs by creating an empty `.auto-version-bump` file in the plugins workspace (i.e., `workspaces/${WORKSPACE}/.auto-version-bump`).
## Version Bumping

View File

@ -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.1",
"fs-extra": "11.3.0",
"husky": "^9.0.11",
"js-yaml": "^4.1.0",
"lint-staged": "^15.2.2",

View File

@ -1,175 +0,0 @@
#!/usr/bin/env node
/*
* 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 fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { parseArgs } from 'util';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const ARCHIVED_FILE = path.join(
__dirname,
'..',
'.github',
'archived-plugins.json',
);
const ARCHIVED_WORKSPACES_FILE = path.join(
__dirname,
'..',
'ARCHIVED_WORKSPACES.md',
);
async function appendToArchivedWorkspacesMd(entries) {
console.log('Updating ARCHIVED_WORKSPACES.md...');
const tableRows = entries.map(entry => {
const workspace = entry.workspace;
const packageName = entry.pluginName;
const reason = entry.reason || 'No longer maintained';
const sourceLink = `[${entry.gitTag}](https://github.com/backstage/community-plugins/tree/${entry.gitTag}/workspaces/${workspace})`;
return `| ${workspace} | \`${packageName}\` | ${reason} | ${sourceLink} |`;
});
const newContent = `${tableRows.join('\n')}\n`;
await fs.appendFile(ARCHIVED_WORKSPACES_FILE, newContent);
console.log(`Added ${entries.length} entries to ARCHIVED_WORKSPACES.md`);
}
async function getPackagesFromWorkspace(workspace, targetPlugin = null) {
const plugins = [];
const pluginsDir = path.join(
__dirname,
'..',
'workspaces',
workspace,
'plugins',
);
const pluginDirs = await fs.readdir(pluginsDir);
for (const pluginDir of pluginDirs) {
const pluginPath = path.join(pluginsDir, pluginDir);
const stat = await fs.stat(pluginPath);
if (!stat.isDirectory()) {
continue;
}
const packageJsonPath = path.join(pluginPath, 'package.json');
const packageData = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
if (packageData.name.startsWith('@backstage-community/')) {
const pluginName = packageData.name.replace('@backstage-community/', '');
// if a plugin specified, only process if plugin name matches
if (targetPlugin && pluginName !== targetPlugin) {
continue;
}
plugins.push({
name: packageData.name,
version: packageData.version,
workspace,
plugin: pluginDir,
});
}
}
return plugins;
}
async function addArchivedEntry(entries) {
const content = await fs.readFile(ARCHIVED_FILE, 'utf8');
const archivedData = JSON.parse(content);
for (const entry of entries) {
console.log(`Adding new entry for ${entry.pluginName}`);
archivedData.archived.push(entry);
}
await fs.writeFile(ARCHIVED_FILE, JSON.stringify(archivedData, null, 2));
console.log(`Updated ${ARCHIVED_FILE}`);
}
async function main() {
try {
const { positionals } = parseArgs({
args: process.argv.slice(2),
options: {
help: {
type: 'boolean',
short: 'h',
},
},
allowPositionals: true,
});
const workspace = positionals[0];
let plugin = positionals[1];
let reason = positionals[2] || 'No longer maintained';
// If second argument looks like a reason (contains spaces), treat it as reason
if (plugin && plugin.includes(' ')) {
reason = plugin;
plugin = null;
}
console.log(`Archiving workspace ${workspace}...`);
console.log(`Reason: ${reason}`);
// Get packages from workspace
const packages = await getPackagesFromWorkspace(workspace, plugin);
if (packages.length === 0) {
console.log('No packages found to archive.');
return;
}
const entries = packages.map(pkg => ({
pluginName: pkg.name,
version: pkg.version,
workspace: pkg.workspace,
plugin: pkg.plugin,
gitTag: `${pkg.name}@${pkg.version}`,
reason,
archivedDate: new Date().toISOString().split('T')[0],
}));
await addArchivedEntry(entries);
await appendToArchivedWorkspacesMd(entries);
console.log(`\nSuccessfully archived ${entries.length} package(s):`);
entries.forEach(entry => {
console.log(
` - ${entry.pluginName} (${entry.workspace}/${entry.plugin}) - Tag: ${entry.gitTag}`,
);
});
} catch (error) {
if (error.message.includes('parsing arguments')) {
// Already handled above
return;
}
console.error('Error:', error.message);
process.exit(1);
}
}
main();

View File

@ -1,51 +0,0 @@
#!/bin/bash
set -e
# Get script directory and archived file path
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ARCHIVED_FILE="$(dirname "$(dirname "$SCRIPT_DIR")")/.github/archived-plugins.json"
# Check if dry run (first argument)
DRY_RUN=${1:-false}
if [[ "$DRY_RUN" == "--dry-run" ]]; then
DRY_RUN=true
echo "DRY RUN MODE"
else
DRY_RUN=false
fi
echo "Processing archived packages..."
# Extract unique plugins from archived-plugins.json file
# Format: package_name|workspace|plugin|reason
jq -r '
.archived
| unique_by(.pluginName)
| .[]
| "\(.pluginName)|\(.workspace)|\(.plugin)|\(.reason)"
' "$ARCHIVED_FILE" | while IFS='|' read -r package_name workspace plugin reason; do
# Check if already deprecated
if npm view "$package_name" deprecated 2>/dev/null | grep -q "true\|deprecated"; then
echo "Already deprecated: $package_name"
continue
fi
# Generate deprecation message
message="This package has been archived from the backstage/community-plugins repository"
[[ -n "$plugin" ]] && message="$message (plugin: $plugin)"
[[ -n "$reason" ]] && message="$message. Reason: $reason"
message="$message."
if [[ "$DRY_RUN" == "true" ]]; then
echo "Would deprecate: $package_name"
echo " Message: $message"
else
echo "Deprecating: $package_name"
npm deprecate "$package_name" "$message"
echo "Done: $package_name"
fi
done
echo "Complete!"

View File

@ -0,0 +1,5 @@
---
'@backstage-community/plugin-acr': patch
---
Updated dependency `@testing-library/jest-dom` to `6.6.4`.

View File

@ -1,5 +0,0 @@
---
'@backstage-community/plugin-acr': patch
---
Updated dependency `@testing-library/jest-dom` to `6.7.0`.

View File

@ -0,0 +1,5 @@
---
'@backstage-community/plugin-acr': minor
---
Backstage version bump to v1.41.1

View File

@ -1,15 +1,5 @@
### Dependencies
## 1.16.0
### Minor Changes
- 4000b53: Backstage version bump to v1.41.1
### Patch Changes
- 6877ddc: Updated dependency `@testing-library/jest-dom` to `6.6.4`.
## 1.15.2
### Patch Changes

View File

@ -1,7 +1,7 @@
{
"name": "@backstage-community/plugin-acr",
"description": "A Backstage plugin that displays information about your container images available in the Azure Container Registry",
"version": "1.16.0",
"version": "1.15.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.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.7.0",
"@testing-library/jest-dom": "6.6.4",
"@testing-library/react": "14.3.1",
"@testing-library/react-hooks": "8.0.1",
"@testing-library/user-event": "14.6.1",

View File

@ -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.7.0"
"@testing-library/jest-dom": "npm:6.6.4"
"@testing-library/react": "npm:14.3.1"
"@testing-library/react-hooks": "npm:8.0.1"
"@testing-library/user-event": "npm:14.6.1"
@ -11690,17 +11690,18 @@ __metadata:
languageName: node
linkType: hard
"@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"
"@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"
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/c994f028b6f2d49c18c9fd6050af7f3316fb0afd03d0ba15d03b177f0f046a0308302dd52ab289fad8794e16a88e4d724b5f23caa007cf343a4b5e435efb84d9
checksum: 10/5e67112c789f884fb75b279c2cddfdd0995a012a7847a03c474e4134f0d213934ee70c97433bca26b45e3a5ffa56faafe6499c8e57841179c4f2bd80eef429cd
languageName: node
linkType: hard

View File

@ -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.1.1",
"@backstage/plugin-scaffolder-backend": "^2.0.0",
"@backstage/repo-tools": "^0.14.0",
"@changesets/cli": "^2.28.1",
"@eslint/js": "^9.19.0",

View File

@ -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.1.1",
"@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-pg": "^0.5.45",

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
---
'@backstage-community/plugin-analytics-module-matomo': patch
'@backstage-community/plugin-analytics-provider-segment': patch
---
Updated dependency `@testing-library/jest-dom` to `6.7.0`.

View File

@ -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.7.0",
"@testing-library/jest-dom": "6.6.4",
"@types/node": "22.15.29",
"cross-fetch": "4.0.0",
"msw": "1.3.5"

View File

@ -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.7.0",
"@testing-library/jest-dom": "6.6.4",
"@testing-library/react": "14.3.1",
"@testing-library/user-event": "14.6.1",
"@types/node": "22.15.29",

View File

@ -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.7.0"
"@testing-library/jest-dom": "npm:6.6.4"
"@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.7.0"
"@testing-library/jest-dom": "npm:6.6.4"
"@testing-library/react": "npm:14.3.1"
"@testing-library/user-event": "npm:14.6.1"
"@types/node": "npm:22.15.29"
@ -6491,17 +6491,18 @@ __metadata:
languageName: node
linkType: hard
"@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"
"@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"
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/c994f028b6f2d49c18c9fd6050af7f3316fb0afd03d0ba15d03b177f0f046a0308302dd52ab289fad8794e16a88e4d724b5f23caa007cf343a4b5e435efb84d9
checksum: 10/5e67112c789f884fb75b279c2cddfdd0995a012a7847a03c474e4134f0d213934ee70c97433bca26b45e3a5ffa56faafe6499c8e57841179c4f2bd80eef429cd
languageName: node
linkType: hard

File diff suppressed because one or more lines are too long

View File

@ -1,4 +0,0 @@
plugins:
- checksum: 8af7b3f2d7d19cacc7a3712f871efcb6208ba283a1f532260b0cba80c2cb66ed772b207b5ba41b8c5d64dd8d5e0c0e15bbb445bd14afac491712965211ba027c
path: .yarn/plugins/@yarnpkg/plugin-backstage.cjs
spec: 'https://versions.backstage.io/v1/releases/1.42.3/yarn-plugin'

View File

@ -1,7 +1,6 @@
app:
title: Azure DevOps Example App
baseUrl: http://localhost:3000
packages: all
organization:
name: Azure DevOps Example

View File

@ -1,3 +1,3 @@
{
"version": "1.42.3"
"version": "1.41.1"
}

View File

@ -36,9 +36,9 @@
"directory": "workspaces/azure-devops"
},
"devDependencies": {
"@backstage/cli": "backstage:^",
"@backstage/e2e-test-utils": "backstage:^",
"@backstage/repo-tools": "backstage:^",
"@backstage/cli": "^0.33.1",
"@backstage/e2e-test-utils": "^0.1.1",
"@backstage/repo-tools": "^0.15.0",
"@changesets/cli": "^2.27.1",
"knip": "^5.27.4",
"node-gyp": "^10.0.0",
@ -58,5 +58,9 @@
"*.{json,md}": [
"prettier --write"
]
},
"dependencies": {
"@backstage-community/plugin-azure-devops": "workspace:^",
"@backstage-community/plugin-azure-devops-backend": "workspace:^"
}
}

View File

@ -20,29 +20,29 @@
},
"dependencies": {
"@backstage-community/plugin-azure-devops": "workspace:^",
"@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:^",
"@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",
@ -52,7 +52,7 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/test-utils": "backstage:^",
"@backstage/test-utils": "^1.7.10",
"@playwright/test": "^1.32.3",
"@testing-library/dom": "^9.0.0",
"@testing-library/jest-dom": "^6.0.0",

View File

@ -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": "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:^",
"@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",
"app": "link:../app",
"better-sqlite3": "^9.0.0",
"dockerode": "^3.3.1",
@ -53,7 +53,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "backstage:^"
"@backstage/cli": "^0.33.1"
},
"files": [
"dist"

View File

@ -1,16 +1,5 @@
# @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

View File

@ -1,6 +1,6 @@
{
"name": "@backstage-community/plugin-azure-devops-backend",
"version": "0.20.0",
"version": "0.19.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": "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:^",
"@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",
"@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": "backstage:^",
"@backstage/cli": "backstage:^",
"@backstage/backend-test-utils": "^1.7.0",
"@backstage/cli": "^0.33.1",
"@types/lodash": "^4.14.151",
"@types/mime-types": "^2.1.0",
"@types/supertest": "^6.0.0",

View File

@ -1,11 +1,5 @@
# @backstage-community/plugin-azure-devops-common
## 0.14.0
### Minor Changes
- 8367480: Backstage version bump to v1.42.3
## 0.13.0
### Minor Changes

View File

@ -1,6 +1,6 @@
{
"name": "@backstage-community/plugin-azure-devops-common",
"version": "0.14.0",
"version": "0.13.0",
"backstage": {
"role": "common-library",
"pluginId": "azure-devops",
@ -41,11 +41,11 @@
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/catalog-model": "backstage:^",
"@backstage/plugin-catalog-common": "backstage:^",
"@backstage/plugin-permission-common": "backstage:^"
"@backstage/catalog-model": "^1.7.5",
"@backstage/plugin-catalog-common": "^1.1.5",
"@backstage/plugin-permission-common": "^0.9.1"
},
"devDependencies": {
"@backstage/cli": "backstage:^"
"@backstage/cli": "^0.33.1"
}
}

View File

@ -1,16 +1,5 @@
# @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

View File

@ -412,43 +412,3 @@ 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
```

View File

@ -1,6 +1,6 @@
{
"name": "@backstage-community/plugin-azure-devops",
"version": "0.19.0",
"version": "0.18.0",
"backstage": {
"role": "frontend-plugin",
"pluginId": "azure-devops",
@ -52,14 +52,14 @@
},
"dependencies": {
"@backstage-community/plugin-azure-devops-common": "workspace:^",
"@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:^",
"@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",
"@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": "backstage:^",
"@backstage/dev-utils": "backstage:^",
"@backstage/test-utils": "backstage:^",
"@backstage/cli": "^0.33.1",
"@backstage/dev-utils": "^1.1.12",
"@backstage/test-utils": "^1.7.10",
"@testing-library/dom": "^10.0.0",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^15.0.0",

View File

@ -7,19 +7,17 @@
import { AnyApiFactory } from '@backstage/core-plugin-api';
import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
import { ApiFactory } from '@backstage/core-plugin-api';
import { ConfigurableExtensionDataRef } from '@backstage/frontend-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: OverridableFrontendPlugin<
const _default: FrontendPlugin<
{},
{},
{
@ -28,17 +26,15 @@ const _default: OverridableFrontendPlugin<
name: undefined;
config: {};
configInput: {};
output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
output: ConfigurableExtensionDataRef<
AnyApiFactory,
'core.api.factory',
{}
>;
inputs: {};
params: <
TApi,
TImpl extends TApi,
TDeps extends {
[x: string]: unknown;
},
>(
params: ApiFactory<TApi, TImpl, TDeps>,
) => ExtensionBlueprintParams<AnyApiFactory>;
params: {
factory: AnyApiFactory;
};
}>;
'entity-card:azure-devops/readme': ExtensionDefinition<{
kind: 'entity-card';
@ -52,22 +48,22 @@ const _default: OverridableFrontendPlugin<
type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<
EntityCardType,
'catalog.entity-card-type',
{
@ -97,31 +93,35 @@ const _default: OverridableFrontendPlugin<
group?: string | false | undefined;
};
output:
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ExtensionDataRef<string, 'core.routing.path', {}>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<
string,
'catalog.entity-content-title',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>
| ExtensionDataRef<string, 'catalog.entity-content-title', {}>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<
string,
'catalog.entity-content-group',
{
@ -130,12 +130,10 @@ const _default: OverridableFrontendPlugin<
>;
inputs: {};
params: {
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?:
loader: () => Promise<JSX.Element>;
defaultPath: string;
defaultTitle: string;
defaultGroup?:
| (string & {})
| 'development'
| 'deployment'
@ -144,7 +142,6 @@ const _default: OverridableFrontendPlugin<
| 'operation'
| 'observability'
| undefined;
loader: () => Promise<JSX.Element>;
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
filter?: EntityPredicate | ((entity: Entity) => boolean) | undefined;
};
@ -165,31 +162,35 @@ const _default: OverridableFrontendPlugin<
group?: string | false | undefined;
};
output:
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ExtensionDataRef<string, 'core.routing.path', {}>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<
string,
'catalog.entity-content-title',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>
| ExtensionDataRef<string, 'catalog.entity-content-title', {}>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<
string,
'catalog.entity-content-group',
{
@ -198,12 +199,10 @@ const _default: OverridableFrontendPlugin<
>;
inputs: {};
params: {
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?:
loader: () => Promise<JSX.Element>;
defaultPath: string;
defaultTitle: string;
defaultGroup?:
| (string & {})
| 'development'
| 'deployment'
@ -212,7 +211,6 @@ const _default: OverridableFrontendPlugin<
| 'operation'
| 'observability'
| undefined;
loader: () => Promise<JSX.Element>;
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
filter?: EntityPredicate | ((entity: Entity) => boolean) | undefined;
};
@ -233,31 +231,35 @@ const _default: OverridableFrontendPlugin<
group?: string | false | undefined;
};
output:
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ExtensionDataRef<string, 'core.routing.path', {}>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<
string,
'catalog.entity-content-title',
{}
>
| ConfigurableExtensionDataRef<
(entity: Entity) => boolean,
'catalog.entity-filter-function',
{
optional: true;
}
>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<
string,
'catalog.entity-filter-expression',
{
optional: true;
}
>
| ExtensionDataRef<string, 'catalog.entity-content-title', {}>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<
string,
'catalog.entity-content-group',
{
@ -266,12 +268,10 @@ const _default: OverridableFrontendPlugin<
>;
inputs: {};
params: {
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?:
loader: () => Promise<JSX.Element>;
defaultPath: string;
defaultTitle: string;
defaultGroup?:
| (string & {})
| 'development'
| 'deployment'
@ -280,7 +280,6 @@ const _default: OverridableFrontendPlugin<
| 'operation'
| 'observability'
| undefined;
loader: () => Promise<JSX.Element>;
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
filter?: EntityPredicate | ((entity: Entity) => boolean) | undefined;
};
@ -295,9 +294,9 @@ const _default: OverridableFrontendPlugin<
path?: string | undefined;
};
output:
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ExtensionDataRef<string, 'core.routing.path', {}>
| ExtensionDataRef<
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
@ -306,8 +305,7 @@ const _default: OverridableFrontendPlugin<
>;
inputs: {};
params: {
defaultPath?: [Error: "Use the 'path' param instead"] | undefined;
path: string;
defaultPath: string;
loader: () => Promise<JSX.Element>;
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
};

View File

@ -16,6 +16,7 @@
import {
ApiBlueprint,
createApiFactory,
PageBlueprint,
createFrontendPlugin,
discoveryApiRef,
@ -35,8 +36,8 @@ import { isAzureDevOpsAvailable, isAzurePipelinesAvailable } from '../plugin';
/** @alpha */
export const azureDevOpsApi = ApiBlueprint.make({
params: defineParams =>
defineParams({
params: {
factory: createApiFactory({
api: azureDevOpsApiRef,
deps: {
discoveryApi: discoveryApiRef,
@ -45,12 +46,13 @@ export const azureDevOpsApi = ApiBlueprint.make({
factory: ({ discoveryApi, fetchApi }) =>
new AzureDevOpsClient({ discoveryApi, fetchApi }),
}),
},
});
/** @alpha */
export const azureDevOpsPullRequestPage = PageBlueprint.make({
params: {
path: '/azure-pull-requests',
defaultPath: '/azure-pull-requests',
routeRef: convertLegacyRouteRef(azurePullRequestDashboardRouteRef),
loader: () =>
import('../components/PullRequestsPage').then(m =>
@ -63,8 +65,8 @@ export const azureDevOpsPullRequestPage = PageBlueprint.make({
export const azureDevOpsPipelinesEntityContent = EntityContentBlueprint.make({
name: 'pipelines',
params: {
path: '/pipelines',
title: 'Pipelines',
defaultPath: '/pipelines',
defaultTitle: 'Pipelines',
filter: isAzurePipelinesAvailable,
loader: () =>
import('../components/EntityPageAzurePipelines').then(m =>
@ -77,8 +79,8 @@ export const azureDevOpsPipelinesEntityContent = EntityContentBlueprint.make({
export const azureDevOpsGitTagsEntityContent = EntityContentBlueprint.make({
name: 'git-tags',
params: {
path: '/git-tags',
title: 'Git Tags',
defaultPath: '/git-tags',
defaultTitle: 'Git Tags',
filter: isAzureDevOpsAvailable,
loader: () =>
import('../components/EntityPageAzureGitTags').then(m =>
@ -92,8 +94,8 @@ export const azureDevOpsPullRequestsEntityContent = EntityContentBlueprint.make(
{
name: 'pull-requests',
params: {
path: '/pull-requests',
title: 'Pull Requests',
defaultPath: '/pull-requests',
defaultTitle: 'Pull Requests',
filter: isAzureDevOpsAvailable,
loader: () =>
import('../components/EntityPageAzurePullRequests').then(m =>

View File

@ -1,16 +1,5 @@
# @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

View File

@ -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.11.0",
"version": "0.10.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": "backstage:^",
"@backstage/catalog-model": "backstage:^",
"@backstage/config": "backstage:^",
"@backstage/integration": "backstage:^",
"@backstage/plugin-catalog-common": "backstage:^",
"@backstage/plugin-catalog-node": "backstage:^",
"@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",
"lodash": "^4.17.21"
},
"devDependencies": {
"@backstage/backend-test-utils": "backstage:^",
"@backstage/cli": "backstage:^"
"@backstage/backend-test-utils": "^1.7.0",
"@backstage/cli": "^0.33.1"
},
"files": [
"dist"

View File

@ -1,11 +1,5 @@
# @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

View File

@ -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.13.0",
"version": "0.12.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": "backstage:^",
"@backstage/config": "backstage:^",
"@backstage/errors": "backstage:^",
"@backstage/integration": "backstage:^",
"@backstage/plugin-scaffolder-node": "backstage:^",
"@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",
"azure-devops-node-api": "^14.1.0",
"yaml": "^2.6.0"
},
"devDependencies": {
"@backstage/cli": "backstage:^",
"@backstage/plugin-scaffolder-node-test-utils": "backstage:^"
"@backstage/cli": "^0.33.1",
"@backstage/plugin-scaffolder-node-test-utils": "^0.3.1"
},
"files": [
"dist"

View File

@ -1,11 +1,5 @@
# @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

View File

@ -1,5 +1,5 @@
{
"version": "0.6.0",
"version": "0.5.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": "backstage:^",
"@backstage/plugin-scaffolder-node": "backstage:^",
"@backstage/backend-plugin-api": "^1.4.1",
"@backstage/plugin-scaffolder-node": "^0.10.0",
"fs-extra": "^11.3.0",
"yaml": "^2.6.0",
"zod": "^3.24.3"
},
"devDependencies": {
"@backstage/cli": "backstage:^",
"@backstage/plugin-scaffolder-node-test-utils": "backstage:^",
"@backstage/cli": "^0.33.1",
"@backstage/plugin-scaffolder-node-test-utils": "^0.3.1",
"@types/fs-extra": "^11"
},
"files": [

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
---
'@backstage-community/plugin-azure-storage-explorer': minor
'@backstage-community/plugin-azure-storage-explorer-backend': minor
---
Backstage version bump to v1.41.1

View File

@ -1,3 +1,3 @@
{
"version": "1.41.1"
"version": "1.40.2"
}

View File

@ -36,9 +36,9 @@
"directory": "workspaces/azure-storage-explorer"
},
"devDependencies": {
"@backstage/cli": "^0.33.1",
"@backstage/cli": "^0.33.0",
"@backstage/e2e-test-utils": "^0.1.1",
"@backstage/repo-tools": "^0.15.0",
"@backstage/repo-tools": "^0.14.0",
"@changesets/cli": "^2.27.1",
"knip": "^5.27.4",
"node-gyp": "^9.0.0",

View File

@ -20,29 +20,29 @@
},
"dependencies": {
"@backstage-community/plugin-azure-storage-explorer": "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": "^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",
"@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.10",
"@backstage/test-utils": "^1.7.9",
"@playwright/test": "^1.32.3",
"@testing-library/dom": "^9.0.0",
"@testing-library/jest-dom": "^6.0.0",

View File

@ -22,27 +22,27 @@
},
"dependencies": {
"@backstage-community/plugin-azure-storage-explorer-backend": "workspace:^",
"@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",
"@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",
"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.1"
"@backstage/cli": "^0.33.0"
},
"files": [
"dist"

View File

@ -40,19 +40,19 @@
"dependencies": {
"@azure/identity": "4.10.2",
"@azure/storage-blob": "12.27.0",
"@backstage/backend-defaults": "^0.11.1",
"@backstage/backend-plugin-api": "^1.4.1",
"@backstage/config": "^1.3.3",
"@backstage/backend-defaults": "^0.11.0",
"@backstage/backend-plugin-api": "^1.4.0",
"@backstage/config": "^1.3.2",
"@backstage/errors": "^1.2.7",
"@types/express": "*",
"express": "^4.17.1",
"express-promise-router": "^4.1.0"
},
"devDependencies": {
"@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",
"@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",
"@types/supertest": "^6.0.0",
"supertest": "^7.0.0"
},

View File

@ -39,10 +39,10 @@
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/core-components": "^0.17.4",
"@backstage/core-plugin-api": "^1.10.9",
"@backstage/core-components": "^0.17.3",
"@backstage/core-plugin-api": "^1.10.8",
"@backstage/errors": "^1.2.7",
"@backstage/theme": "^0.6.7",
"@backstage/theme": "^0.6.6",
"@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.1",
"@backstage/core-app-api": "^1.18.0",
"@backstage/dev-utils": "^1.1.12",
"@backstage/test-utils": "^1.7.10",
"@backstage/cli": "^0.33.0",
"@backstage/core-app-api": "^1.17.1",
"@backstage/dev-utils": "^1.1.11",
"@backstage/test-utils": "^1.7.9",
"@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

View File

@ -1,8 +0,0 @@
# 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)

View File

@ -1,11 +0,0 @@
{
"$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": []
}

View File

@ -1,8 +0,0 @@
.git
.yarn/cache
.yarn/install-state.gz
node_modules
packages/*/src
packages/*/node_modules
plugins
*.local.yaml

View File

@ -1 +0,0 @@
playwright.config.ts

View File

@ -1,3 +0,0 @@
module.exports = {
root: true,
};

View File

@ -1,56 +0,0 @@
# 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/

View File

@ -1,6 +0,0 @@
.vscode
coverage
dist
dist-types
knip-report.md
report.api.md

View File

@ -1,13 +0,0 @@
# [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
```

View File

@ -1,64 +0,0 @@
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

View File

@ -1,3 +0,0 @@
{
"version": "1.41.0"
}

View File

@ -1,3 +0,0 @@
{
"knip-reports": true
}

View File

@ -1,13 +0,0 @@
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

View File

@ -1,66 +0,0 @@
{
"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
}
}
}
}

View File

@ -1,12 +0,0 @@
# 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)!

View File

@ -1 +0,0 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);

View File

@ -1,7 +0,0 @@
# @backstage-community/plugin-bookmarks
## 0.2.0
### Minor Changes
- 80b21b2: Initial release

View File

@ -1,58 +0,0 @@
# 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.
![A screenshot of the Bookmarks plugin](https://i.imgur.com/guMtiax.png)
## 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

View File

@ -1,65 +0,0 @@
/*
* 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>
);

View File

@ -1,16 +0,0 @@
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();

View File

@ -1,23 +0,0 @@
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]

View File

@ -1,2 +0,0 @@
# Knip report

View File

@ -1,74 +0,0 @@
{
"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"
]
}

View File

@ -1,48 +0,0 @@
## 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;
};
};
```

View File

@ -1,187 +0,0 @@
/*
* 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);
});
});

View File

@ -1,114 +0,0 @@
/*
* 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}
/>
);
});

View File

@ -1,43 +0,0 @@
/*
* 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();
});
});

View File

@ -1,49 +0,0 @@
/*
* 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>
);

View File

@ -1,67 +0,0 @@
/*
* 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();
});
});

View File

@ -1,91 +0,0 @@
/*
* 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>
);
};

View File

@ -1,64 +0,0 @@
/*
* 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();
});
});

View File

@ -1,78 +0,0 @@
/*
* 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%',
}}
/>
</>
);
});

View File

@ -1,59 +0,0 @@
/*
* 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);
});
});

View File

@ -1,77 +0,0 @@
/*
* 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>
);
};

View File

@ -1,98 +0,0 @@
/*
* 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',
});
});
});

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