New plugin workspace: manage (#2569)
Signed-off-by: Gustaf Räntilä <g.rantila@gmail.com>
This commit is contained in:
		
							parent
							
								
									a8e2f2cad0
								
							
						
					
					
						commit
						78f2046a4e
					
				|  | @ -37,6 +37,7 @@ yarn.lock                                           @backstage/community-plugins | |||
| /workspaces/keycloak                                @backstage/community-plugins-maintainers  @AndrienkoAleksandr @schultzp2020 @dzemanov | ||||
| /workspaces/kiali                                   @backstage/community-plugins-maintainers  @aljesusg @josunect @leandroberetta | ||||
| /workspaces/linguist                                @backstage/community-plugins-maintainers  @awanlin | ||||
| /workspaces/manage                                  @backstage/community-plugins-maintainers  @grantila | ||||
| /workspaces/matomo                                  @backstage/community-plugins-maintainers  @yashoswalyo @deshmukhmayur @riginoommen | ||||
| /workspaces/mend                                    @backstage/community-plugins-maintainers  @dariuszsobkowicz | ||||
| /workspaces/mta                                     @backstage/community-plugins-maintainers  @ibolton336 | ||||
|  |  | |||
|  | @ -0,0 +1,8 @@ | |||
| # Changesets | ||||
| 
 | ||||
| Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works | ||||
| with multi-package repos, or single-package repos to help you version and publish your code. You can | ||||
| find the full documentation for it [in our repository](https://github.com/changesets/changesets) | ||||
| 
 | ||||
| We have a quick list of common questions to get you started engaging with this project in | ||||
| [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) | ||||
|  | @ -0,0 +1,14 @@ | |||
| { | ||||
|   "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", | ||||
|   "changelog": "@changesets/cli/changelog", | ||||
|   "commit": false, | ||||
|   "fixed": [], | ||||
|   "linked": [], | ||||
|   "access": "public", | ||||
|   "baseBranch": "main", | ||||
|   "updateInternalDependencies": "patch", | ||||
|   "privatePackages": { | ||||
|     "tag": false, | ||||
|     "version": false | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,8 @@ | |||
| .git | ||||
| .yarn/cache | ||||
| .yarn/install-state.gz | ||||
| node_modules | ||||
| packages/*/src | ||||
| packages/*/node_modules | ||||
| plugins | ||||
| *.local.yaml | ||||
|  | @ -0,0 +1,10 @@ | |||
| root = true | ||||
| 
 | ||||
| [*] | ||||
| end_of_line = lf | ||||
| insert_final_newline = true | ||||
| 
 | ||||
| [*.{js,json,yml}] | ||||
| charset = utf-8 | ||||
| indent_style = space | ||||
| indent_size = 2 | ||||
|  | @ -0,0 +1 @@ | |||
| playwright.config.ts | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('../../.eslintrc.cjs'); | ||||
|  | @ -0,0 +1,54 @@ | |||
| # macOS | ||||
| .DS_Store | ||||
| 
 | ||||
| # Logs | ||||
| logs | ||||
| *.log | ||||
| npm-debug.log* | ||||
| yarn-debug.log* | ||||
| yarn-error.log* | ||||
| lerna-debug.log* | ||||
| 
 | ||||
| # Coverage directory generated when running tests with coverage | ||||
| coverage | ||||
| 
 | ||||
| # Dependencies | ||||
| node_modules/ | ||||
| 
 | ||||
| # Yarn 3 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/ | ||||
|  | @ -0,0 +1,5 @@ | |||
| dist | ||||
| dist-types | ||||
| coverage | ||||
| .vscode | ||||
| .eslintrc.js | ||||
|  | @ -0,0 +1,22 @@ | |||
| # Manage page | ||||
| 
 | ||||
| This plugin offers a place for developers to manage things they and their team own. | ||||
| 
 | ||||
| Read the [documentation](./plugins/manage/README.md) for the frontend plugin. | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## Packages | ||||
| 
 | ||||
| - [manage](./plugins/manage/README.md) - The frontend plugin for the Manage page | ||||
| - [manage-module-tech-insights](./plugins/manage-module-tech-insights/README.md) - The tech insights module for the Manage plugin, to display tech insight checks in the entity tables, and/or display aggregated gauges. | ||||
| - [manage-react](./plugins/manage-react/README.md) - A module for extending the Manage page with custom components, features, entity table columns, etc. | ||||
| 
 | ||||
| ## Local Development | ||||
| 
 | ||||
| To start the Backstage App, run: | ||||
| 
 | ||||
| ```sh | ||||
| yarn install | ||||
| yarn dev | ||||
| ``` | ||||
|  | @ -0,0 +1,84 @@ | |||
| app: | ||||
|   title: Tech insights Example App | ||||
|   baseUrl: http://localhost:3000 | ||||
| 
 | ||||
| organization: | ||||
|   name: Tech insights Example | ||||
| 
 | ||||
| backend: | ||||
|   baseUrl: http://localhost:7007 | ||||
|   listen: | ||||
|     port: 7007 | ||||
|   csp: | ||||
|     connect-src: ["'self'", 'http:', 'https:'] | ||||
|   cors: | ||||
|     origin: http://localhost:3000 | ||||
|     methods: [GET, HEAD, PATCH, POST, PUT, DELETE] | ||||
|     credentials: true | ||||
|   database: | ||||
|     client: better-sqlite3 | ||||
|     connection: ':memory:' | ||||
| 
 | ||||
| integrations: | ||||
|   github: | ||||
|     - host: github.com | ||||
|       token: ${GITHUB_TOKEN} | ||||
| 
 | ||||
| techdocs: | ||||
|   builder: 'local' | ||||
|   generator: | ||||
|     runIn: 'local' | ||||
|   publisher: | ||||
|     type: 'local' | ||||
| 
 | ||||
| auth: | ||||
|   providers: | ||||
|     guest: | ||||
|       userEntityRef: user:default/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] | ||||
| 
 | ||||
| techInsights: | ||||
|   factRetrievers: | ||||
|     entityOwnershipFactRetriever: | ||||
|       cadence: '*/1 * * * *' | ||||
|       lifecycle: { timeToLive: { weeks: 2 } } | ||||
|     entityMetadataFactRetriever: | ||||
|       cadence: '*/1 * * * *' | ||||
|       lifecycle: { timeToLive: { weeks: 2 } } | ||||
|     techdocsFactRetriever: | ||||
|       cadence: '*/1 * * * *' | ||||
|       lifecycle: { timeToLive: { weeks: 2 } } | ||||
|     apiDefinitionFactRetriever: | ||||
|       cadence: '*/1 * * * *' | ||||
|       lifecycle: { timeToLive: { weeks: 2 } } | ||||
| #  See packages/backend/src/index.ts for programmatically registration | ||||
| #  factChecker: | ||||
| #    checks: | ||||
| #      groupOwnerCheck: | ||||
| #        type: json-rules-engine | ||||
| #        name: Group Owner Check | ||||
| #        description: Verifies that a group has been set as the spec.owner for this entity | ||||
| #        factIds: | ||||
| #          - entityOwnershipFactRetriever | ||||
| #        rule: | ||||
| #          conditions: | ||||
| #            all: | ||||
| #              - fact: hasGroupOwner | ||||
| #                operator: equal | ||||
| #                value: true | ||||
|  | @ -0,0 +1,3 @@ | |||
| { | ||||
|   "version": "1.35.0" | ||||
| } | ||||
|  | @ -0,0 +1,13 @@ | |||
| apiVersion: backstage.io/v1alpha1 | ||||
| kind: Component | ||||
| metadata: | ||||
|   name: manage | ||||
|   description: An example of a Backstage application. | ||||
|   # Example for optional annotations | ||||
|   # annotations: | ||||
|   #   github.com/project-slug: backstage/backstage | ||||
|   #   backstage.io/techdocs-ref: dir:. | ||||
| spec: | ||||
|   type: website | ||||
|   owner: john@example.com | ||||
|   lifecycle: experimental | ||||
|  | @ -0,0 +1,53 @@ | |||
| --- | ||||
| # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system | ||||
| apiVersion: backstage.io/v1alpha1 | ||||
| kind: System | ||||
| metadata: | ||||
|   name: examples | ||||
| spec: | ||||
|   owner: guests | ||||
| --- | ||||
| # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component | ||||
| apiVersion: backstage.io/v1alpha1 | ||||
| kind: Component | ||||
| metadata: | ||||
|   name: example-website | ||||
| spec: | ||||
|   type: website | ||||
|   lifecycle: experimental | ||||
|   owner: guests | ||||
|   system: examples | ||||
|   providesApis: [example-grpc-api] | ||||
| --- | ||||
| # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api | ||||
| apiVersion: backstage.io/v1alpha1 | ||||
| kind: API | ||||
| metadata: | ||||
|   name: example-grpc-api | ||||
| spec: | ||||
|   type: grpc | ||||
|   lifecycle: experimental | ||||
|   owner: guests | ||||
|   system: examples | ||||
|   definition: | | ||||
|     syntax = "proto3"; | ||||
| 
 | ||||
|     service Exampler { | ||||
|       rpc Example (ExampleMessage) returns (ExampleMessage) {}; | ||||
|     } | ||||
| 
 | ||||
|     message ExampleMessage { | ||||
|       string example = 1; | ||||
|     }; | ||||
| --- | ||||
| # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api | ||||
| apiVersion: backstage.io/v1alpha1 | ||||
| kind: Component | ||||
| metadata: | ||||
|   name: example-service | ||||
|   title: Example service | ||||
| spec: | ||||
|   type: service | ||||
|   lifecycle: experimental | ||||
|   owner: guests | ||||
|   system: examples | ||||
|  | @ -0,0 +1,17 @@ | |||
| --- | ||||
| # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user | ||||
| apiVersion: backstage.io/v1alpha1 | ||||
| kind: User | ||||
| metadata: | ||||
|   name: guest | ||||
| spec: | ||||
|   memberOf: [guests] | ||||
| --- | ||||
| # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group | ||||
| apiVersion: backstage.io/v1alpha1 | ||||
| kind: Group | ||||
| metadata: | ||||
|   name: guests | ||||
| spec: | ||||
|   type: team | ||||
|   children: [] | ||||
|  | @ -0,0 +1,64 @@ | |||
| { | ||||
|   "name": "@internal/manage", | ||||
|   "version": "1.0.0", | ||||
|   "private": true, | ||||
|   "engines": { | ||||
|     "node": "18 || 20" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "dev": "yarn workspaces foreach -A --include backend --include app --parallel --jobs unlimited -v -i run start", | ||||
|     "start": "yarn workspace app start", | ||||
|     "start-backend": "yarn workspace backend start", | ||||
|     "build:backend": "yarn workspace backend build", | ||||
|     "tsc": "tsc", | ||||
|     "tsc:full": "tsc --skipLibCheck false --incremental false", | ||||
|     "build:all": "backstage-cli repo build --all", | ||||
|     "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/main", | ||||
|     "lint:all": "backstage-cli repo lint", | ||||
|     "prettier:check": "prettier --check .", | ||||
|     "prettier:write": "prettier --write .", | ||||
|     "new": "backstage-cli new --scope @backstage-community", | ||||
|     "build:api-reports": "yarn build:api-reports:only --tsc", | ||||
|     "build:api-reports:only": "backstage-repo-tools api-reports -o ae-wrong-input-file-type,ae-undocumented --validate-release-tags", | ||||
|     "postinstall": "cd ../../ && yarn install" | ||||
|   }, | ||||
|   "workspaces": { | ||||
|     "packages": [ | ||||
|       "packages/*", | ||||
|       "plugins/*" | ||||
|     ] | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "https://github.com/backstage/community-plugins", | ||||
|     "directory": "workspaces/manage" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@backstage/cli": "^0.29.5", | ||||
|     "@backstage/e2e-test-utils": "^0.1.1", | ||||
|     "@backstage/repo-tools": "^0.12.1", | ||||
|     "@changesets/cli": "^2.27.1", | ||||
|     "knip": "^5.27.4", | ||||
|     "node-gyp": "^11.0.0", | ||||
|     "prettier": "^2.3.2", | ||||
|     "typescript": "~5.3.0" | ||||
|   }, | ||||
|   "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" | ||||
|     ] | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,9 @@ | |||
| # The Packages Folder | ||||
| 
 | ||||
| This is where your own applications and centrally managed libraries live, each | ||||
| in a separate folder of its own. | ||||
| 
 | ||||
| From the start there's an `app` folder (for the frontend) and a `backend` folder | ||||
| (for the Node backend), but you can also add more modules in here that house | ||||
| your core additions and adaptations, such as themes, common React component | ||||
| libraries, utilities, and similar. | ||||
|  | @ -0,0 +1 @@ | |||
| public | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); | ||||
|  | @ -0,0 +1,82 @@ | |||
| # app | ||||
| 
 | ||||
| ## 0.0.11 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [7a14237] | ||||
|   - @backstage-community/plugin-tech-insights@0.3.39 | ||||
| 
 | ||||
| ## 0.0.10 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [caa9401] | ||||
|   - @backstage-community/plugin-tech-insights@0.3.38 | ||||
| 
 | ||||
| ## 0.0.9 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [e516773] | ||||
| - Updated dependencies [e516773] | ||||
|   - @backstage-community/plugin-tech-insights@0.3.37 | ||||
| 
 | ||||
| ## 0.0.8 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [42a2c31] | ||||
|   - @backstage-community/plugin-tech-insights@0.3.36 | ||||
| 
 | ||||
| ## 0.0.7 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [1d33996] | ||||
|   - @backstage-community/plugin-tech-insights@0.3.35 | ||||
| 
 | ||||
| ## 0.0.6 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [a84eb44] | ||||
|   - @backstage-community/plugin-tech-insights@0.3.34 | ||||
| 
 | ||||
| ## 0.0.5 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [00d148d] | ||||
|   - @backstage-community/plugin-tech-insights@0.3.33 | ||||
| 
 | ||||
| ## 0.0.4 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [83a5e80] | ||||
|   - @backstage-community/plugin-tech-insights@0.3.32 | ||||
| 
 | ||||
| ## 0.0.3 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [0265767] | ||||
|   - @backstage-community/plugin-tech-insights@0.3.31 | ||||
| 
 | ||||
| ## 0.0.2 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [7ac338c] | ||||
| - Updated dependencies [794cc8b] | ||||
| - Updated dependencies [a8d8d44] | ||||
|   - @backstage-community/plugin-tech-insights@0.3.30 | ||||
| 
 | ||||
| ## 0.0.1 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [cbad35a] | ||||
| - Updated dependencies [cbad35a] | ||||
|   - @backstage-community/plugin-tech-insights@0.3.29 | ||||
|  | @ -0,0 +1,27 @@ | |||
| /* | ||||
|  * Copyright 2020 The Backstage Authors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| import { test, expect } from '@playwright/test'; | ||||
| 
 | ||||
| test('App should render the welcome page', async ({ page }) => { | ||||
|   await page.goto('/'); | ||||
| 
 | ||||
|   const enterButton = page.getByRole('button', { name: 'Enter' }); | ||||
|   await expect(enterButton).toBeVisible(); | ||||
|   await enterButton.click(); | ||||
| 
 | ||||
|   await expect(page.getByText('My Company Catalog')).toBeVisible(); | ||||
| }); | ||||
|  | @ -0,0 +1,19 @@ | |||
| # Knip report | ||||
| 
 | ||||
| ## Unused dependencies (4) | ||||
| 
 | ||||
| | Name                                      | Location     | Severity | | ||||
| | :---------------------------------------- | :----------- | :------- | | ||||
| | @backstage-community/plugin-tech-insights | package.json | error    | | ||||
| | react-router                              | package.json | error    | | ||||
| | react-use                                 | package.json | error    | | ||||
| | history                                   | package.json | error    | | ||||
| 
 | ||||
| ## Unused devDependencies (4) | ||||
| 
 | ||||
| | Name                        | Location     | Severity | | ||||
| | :-------------------------- | :----------- | :------- | | ||||
| | @testing-library/user-event | package.json | error    | | ||||
| | @backstage/test-utils       | package.json | error    | | ||||
| | @testing-library/dom        | package.json | error    | | ||||
| | cross-env                   | package.json | error    | | ||||
|  | @ -0,0 +1,85 @@ | |||
| { | ||||
|   "name": "app", | ||||
|   "version": "0.0.11", | ||||
|   "private": true, | ||||
|   "bundled": true, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "https://github.com/backstage/community-plugins", | ||||
|     "directory": "workspaces/manage/packages/app" | ||||
|   }, | ||||
|   "backstage": { | ||||
|     "role": "frontend" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "start": "backstage-cli package start", | ||||
|     "build": "backstage-cli package build", | ||||
|     "clean": "backstage-cli package clean", | ||||
|     "test": "backstage-cli package test", | ||||
|     "lint": "backstage-cli package lint" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@backstage-community/plugin-github-actions": "^0.6.16", | ||||
|     "@backstage-community/plugin-manage": "workspace:^", | ||||
|     "@backstage-community/plugin-manage-module-tech-insights": "workspace:^", | ||||
|     "@backstage-community/plugin-manage-react": "workspace:^", | ||||
|     "@backstage-community/plugin-tech-insights": "^0.4.0", | ||||
|     "@backstage-community/plugin-tech-radar": "^0.7.4", | ||||
|     "@backstage/app-defaults": "^1.5.16", | ||||
|     "@backstage/catalog-model": "^1.7.3", | ||||
|     "@backstage/cli": "^0.29.5", | ||||
|     "@backstage/core-app-api": "^1.15.4", | ||||
|     "@backstage/core-components": "^0.16.3", | ||||
|     "@backstage/core-plugin-api": "^1.10.3", | ||||
|     "@backstage/integration-react": "^1.2.3", | ||||
|     "@backstage/plugin-api-docs": "^0.12.3", | ||||
|     "@backstage/plugin-catalog": "^1.26.1", | ||||
|     "@backstage/plugin-catalog-common": "^1.1.3", | ||||
|     "@backstage/plugin-catalog-graph": "^0.4.15", | ||||
|     "@backstage/plugin-catalog-import": "^0.12.9", | ||||
|     "@backstage/plugin-catalog-react": "^1.15.1", | ||||
|     "@backstage/plugin-org": "^0.6.35", | ||||
|     "@backstage/plugin-permission-react": "^0.4.30", | ||||
|     "@backstage/plugin-scaffolder": "^1.27.4", | ||||
|     "@backstage/plugin-search": "^1.4.22", | ||||
|     "@backstage/plugin-search-react": "^1.8.5", | ||||
|     "@backstage/plugin-techdocs": "^1.12.1", | ||||
|     "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.20", | ||||
|     "@backstage/plugin-techdocs-react": "^1.2.13", | ||||
|     "@backstage/plugin-user-settings": "^0.8.18", | ||||
|     "@backstage/theme": "^0.6.3", | ||||
|     "@material-ui/core": "^4.12.2", | ||||
|     "@material-ui/icons": "^4.9.1", | ||||
|     "history": "^5.0.0", | ||||
|     "react": "^18.0.2", | ||||
|     "react-dom": "^18.0.2", | ||||
|     "react-router": "^6.3.0", | ||||
|     "react-router-dom": "^6.3.0", | ||||
|     "react-use": "^17.2.4" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@backstage/test-utils": "^1.7.4", | ||||
|     "@playwright/test": "^1.32.3", | ||||
|     "@testing-library/dom": "^9.0.0", | ||||
|     "@testing-library/jest-dom": "^6.0.0", | ||||
|     "@testing-library/react": "^14.0.0", | ||||
|     "@testing-library/user-event": "^14.0.0", | ||||
|     "@types/react-dom": "*", | ||||
|     "cross-env": "^7.0.0" | ||||
|   }, | ||||
|   "browserslist": { | ||||
|     "production": [ | ||||
|       ">0.2%", | ||||
|       "not dead", | ||||
|       "not op_mini all" | ||||
|     ], | ||||
|     "development": [ | ||||
|       "last 1 chrome version", | ||||
|       "last 1 firefox version", | ||||
|       "last 1 safari version" | ||||
|     ] | ||||
|   }, | ||||
|   "files": [ | ||||
|     "dist" | ||||
|   ] | ||||
| } | ||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 13 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 12 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 883 B | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.6 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 15 KiB | 
|  | @ -0,0 +1,60 @@ | |||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|   <head> | ||||
|     <meta charset="utf-8" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <meta name="theme-color" content="#000000" /> | ||||
|     <meta | ||||
|       name="description" | ||||
|       content="Backstage is an open source framework for building developer portals" | ||||
|     /> | ||||
|     <!-- | ||||
|       manifest.json provides metadata used when your web app is installed on a | ||||
|       user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ | ||||
|     --> | ||||
|     <link | ||||
|       rel="manifest" | ||||
|       href="<%= publicPath %>/manifest.json" | ||||
|       crossorigin="use-credentials" | ||||
|     /> | ||||
|     <link rel="icon" href="<%= publicPath %>/favicon.ico" /> | ||||
|     <link rel="shortcut icon" href="<%= publicPath %>/favicon.ico" /> | ||||
|     <link | ||||
|       rel="apple-touch-icon" | ||||
|       sizes="180x180" | ||||
|       href="<%= publicPath %>/apple-touch-icon.png" | ||||
|     /> | ||||
|     <link | ||||
|       rel="icon" | ||||
|       type="image/png" | ||||
|       sizes="32x32" | ||||
|       href="<%= publicPath %>/favicon-32x32.png" | ||||
|     /> | ||||
|     <link | ||||
|       rel="icon" | ||||
|       type="image/png" | ||||
|       sizes="16x16" | ||||
|       href="<%= publicPath %>/favicon-16x16.png" | ||||
|     /> | ||||
|     <link | ||||
|       rel="mask-icon" | ||||
|       href="<%= publicPath %>/safari-pinned-tab.svg" | ||||
|       color="#5bbad5" | ||||
|     /> | ||||
|     <title><%= config.getOptionalString('app.title') ?? 'Backstage' %></title> | ||||
|   </head> | ||||
|   <body> | ||||
|     <noscript>You need to enable JavaScript to run this app.</noscript> | ||||
|     <div id="root"></div> | ||||
|     <!-- | ||||
|       This HTML file is a template. | ||||
|       If you open it directly in the browser, you will see an empty page. | ||||
| 
 | ||||
|       You can add webfonts, meta tags, or analytics to this file. | ||||
|       The build step will place the bundled scripts into the <body> tag. | ||||
| 
 | ||||
|       To begin the development, run `yarn start`. | ||||
|       To create a production bundle, use `yarn build`. | ||||
|     --> | ||||
|   </body> | ||||
| </html> | ||||
|  | @ -0,0 +1,15 @@ | |||
| { | ||||
|   "short_name": "Backstage", | ||||
|   "name": "Backstage", | ||||
|   "icons": [ | ||||
|     { | ||||
|       "src": "favicon.ico", | ||||
|       "sizes": "48x48", | ||||
|       "type": "image/png" | ||||
|     } | ||||
|   ], | ||||
|   "start_url": "./index.html", | ||||
|   "display": "standalone", | ||||
|   "theme_color": "#000000", | ||||
|   "background_color": "#ffffff" | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| # https://www.robotstxt.org/robotstxt.html | ||||
| User-agent: * | ||||
|  | @ -0,0 +1 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="682.667" height="682.667" preserveAspectRatio="xMidYMid meet" version="1.0" viewBox="0 0 512 512"><metadata>Created by potrace 1.11, written by Peter Selinger 2001-2013</metadata><g fill="#000" stroke="none"><path d="M492 4610 c-4 -3 -8 -882 -7 -1953 l0 -1948 850 2 c898 1 945 3 1118 49 505 134 823 531 829 1037 2 136 -9 212 -44 323 -40 125 -89 218 -163 310 -35 43 -126 128 -169 157 -22 15 -43 30 -46 33 -12 13 -131 70 -188 91 l-64 22 60 28 c171 77 317 224 403 404 64 136 92 266 91 425 -5 424 -245 770 -642 923 -79 30 -105 39 -155 50 -11 3 -38 10 -60 15 -22 6 -60 13 -85 17 -25 3 -58 9 -75 12 -36 8 -1643 11 -1653 3z m1497 -743 c236 -68 352 -254 305 -486 -26 -124 -110 -224 -232 -277 -92 -40 -151 -46 -439 -49 l-283 -3 -1 27 c-1 36 -1 760 0 790 l1 23 298 -5 c226 -4 310 -9 351 -20z m-82 -1538 c98 -3 174 -19 247 -52 169 -78 257 -212 258 -395 0 -116 -36 -221 -100 -293 -64 -72 -192 -135 -314 -155 -23 -3 -181 -7 -350 -8 l-308 -2 -1 26 c-6 210 1 874 9 879 9 5 366 6 559 0z" transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"/><path d="M4160 1789 c-275 -24 -499 -263 -503 -536 -1 -115 21 -212 66 -292 210 -369 697 -402 950 -65 77 103 110 199 111 329 0 50 -6 113 -13 140 -16 58 -62 155 -91 193 -33 43 -122 132 -132 132 -5 0 -26 11 -46 25 -85 56 -219 85 -342 74z" transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"/></g></svg> | ||||
| After Width: | Height: | Size: 1.4 KiB | 
|  | @ -0,0 +1,44 @@ | |||
| /* | ||||
|  * Copyright 2024 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 React from 'react'; | ||||
| import { render, waitFor } from '@testing-library/react'; | ||||
| import App from './App'; | ||||
| 
 | ||||
| describe('App', () => { | ||||
|   it('should render', async () => { | ||||
|     process.env = { | ||||
|       NODE_ENV: 'test', | ||||
|       APP_CONFIG: [ | ||||
|         { | ||||
|           data: { | ||||
|             app: { title: 'Test' }, | ||||
|             backend: { baseUrl: 'http://localhost:7007' }, | ||||
|             techdocs: { | ||||
|               storageUrl: 'http://localhost:7007/api/techdocs/static/docs', | ||||
|             }, | ||||
|           }, | ||||
|           context: 'test', | ||||
|         }, | ||||
|       ] as any, | ||||
|     }; | ||||
| 
 | ||||
|     const rendered = render(<App />); | ||||
| 
 | ||||
|     await waitFor(() => { | ||||
|       expect(rendered.baseElement).toBeInTheDocument(); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|  | @ -0,0 +1,145 @@ | |||
| /* | ||||
|  * Copyright 2024 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 React from 'react'; | ||||
| import { Navigate, Route } from 'react-router-dom'; | ||||
| import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; | ||||
| import { | ||||
|   CatalogEntityPage, | ||||
|   CatalogIndexPage, | ||||
|   catalogPlugin, | ||||
| } from '@backstage/plugin-catalog'; | ||||
| import { | ||||
|   CatalogImportPage, | ||||
|   catalogImportPlugin, | ||||
| } from '@backstage/plugin-catalog-import'; | ||||
| import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; | ||||
| import { orgPlugin } from '@backstage/plugin-org'; | ||||
| import { SearchPage } from '@backstage/plugin-search'; | ||||
| import { TechRadarPage } from '@backstage-community/plugin-tech-radar'; | ||||
| import { | ||||
|   TechDocsIndexPage, | ||||
|   techdocsPlugin, | ||||
|   TechDocsReaderPage, | ||||
| } from '@backstage/plugin-techdocs'; | ||||
| import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; | ||||
| import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; | ||||
| import { UserSettingsPage } from '@backstage/plugin-user-settings'; | ||||
| import { apis } from './apis'; | ||||
| import { entityPage } from './components/catalog/EntityPage'; | ||||
| import { searchPage } from './components/search/SearchPage'; | ||||
| import { Root } from './components/Root'; | ||||
| import { | ||||
|   techInsightsPlugin, | ||||
|   TechInsightsScorecardPage, | ||||
| } from '@backstage-community/plugin-tech-insights'; | ||||
| 
 | ||||
| import { | ||||
|   AlertDisplay, | ||||
|   OAuthRequestDialog, | ||||
|   SignInPage, | ||||
| } from '@backstage/core-components'; | ||||
| import { createApp } from '@backstage/app-defaults'; | ||||
| import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; | ||||
| import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; | ||||
| import { RequirePermission } from '@backstage/plugin-permission-react'; | ||||
| import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; | ||||
| import { ManagePage } from '@backstage-community/plugin-manage'; | ||||
| import { Manage } from './components/manage/Manage'; | ||||
| 
 | ||||
| const app = createApp({ | ||||
|   apis, | ||||
|   plugins: [techInsightsPlugin], | ||||
|   bindRoutes({ bind }) { | ||||
|     bind(catalogPlugin.externalRoutes, { | ||||
|       createComponent: scaffolderPlugin.routes.root, | ||||
|       viewTechDoc: techdocsPlugin.routes.docRoot, | ||||
|       createFromTemplate: scaffolderPlugin.routes.selectedTemplate, | ||||
|     }); | ||||
|     bind(apiDocsPlugin.externalRoutes, { | ||||
|       registerApi: catalogImportPlugin.routes.importPage, | ||||
|     }); | ||||
|     bind(scaffolderPlugin.externalRoutes, { | ||||
|       registerComponent: catalogImportPlugin.routes.importPage, | ||||
|       viewTechDoc: techdocsPlugin.routes.docRoot, | ||||
|     }); | ||||
|     bind(orgPlugin.externalRoutes, { | ||||
|       catalogIndex: catalogPlugin.routes.catalogIndex, | ||||
|     }); | ||||
|   }, | ||||
|   components: { | ||||
|     SignInPage: props => <SignInPage {...props} auto providers={['guest']} />, | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| const routes = ( | ||||
|   <FlatRoutes> | ||||
|     <Route path="/" element={<Navigate to="catalog" />} /> | ||||
|     <Route | ||||
|       path="/manage" | ||||
|       element={ | ||||
|         <ManagePage> | ||||
|           <Manage /> | ||||
|         </ManagePage> | ||||
|       } | ||||
|     /> | ||||
|     <Route path="/catalog" element={<CatalogIndexPage />} /> | ||||
|     <Route | ||||
|       path="/catalog/:namespace/:kind/:name" | ||||
|       element={<CatalogEntityPage />} | ||||
|     > | ||||
|       {entityPage} | ||||
|     </Route> | ||||
|     <Route path="/docs" element={<TechDocsIndexPage />} /> | ||||
|     <Route | ||||
|       path="/docs/:namespace/:kind/:name/*" | ||||
|       element={<TechDocsReaderPage />} | ||||
|     > | ||||
|       <TechDocsAddons> | ||||
|         <ReportIssue /> | ||||
|       </TechDocsAddons> | ||||
|     </Route> | ||||
|     <Route path="/create" element={<ScaffolderPage />} /> | ||||
|     <Route path="/api-docs" element={<ApiExplorerPage />} /> | ||||
|     <Route | ||||
|       path="/tech-radar" | ||||
|       element={<TechRadarPage width={1500} height={800} />} | ||||
|     /> | ||||
|     <Route | ||||
|       path="/catalog-import" | ||||
|       element={ | ||||
|         <RequirePermission permission={catalogEntityCreatePermission}> | ||||
|           <CatalogImportPage /> | ||||
|         </RequirePermission> | ||||
|       } | ||||
|     /> | ||||
|     <Route path="/search" element={<SearchPage />}> | ||||
|       {searchPage} | ||||
|     </Route> | ||||
|     <Route path="/settings" element={<UserSettingsPage />} /> | ||||
|     <Route path="/catalog-graph" element={<CatalogGraphPage />} /> | ||||
|     <Route path="/tech-insights" element={<TechInsightsScorecardPage />} /> | ||||
|   </FlatRoutes> | ||||
| ); | ||||
| 
 | ||||
| export default app.createRoot( | ||||
|   <> | ||||
|     <AlertDisplay /> | ||||
|     <OAuthRequestDialog /> | ||||
|     <AppRouter> | ||||
|       <Root>{routes}</Root> | ||||
|     </AppRouter> | ||||
|   </>, | ||||
| ); | ||||
|  | @ -0,0 +1,44 @@ | |||
| /* | ||||
|  * Copyright 2024 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 { | ||||
|   ScmIntegrationsApi, | ||||
|   scmIntegrationsApiRef, | ||||
|   ScmAuth, | ||||
| } from '@backstage/integration-react'; | ||||
| import { | ||||
|   AnyApiFactory, | ||||
|   configApiRef, | ||||
|   createApiFactory, | ||||
| } from '@backstage/core-plugin-api'; | ||||
| import { | ||||
|   createManageTechInsightsApiFactory, | ||||
|   manageTechInsightsApiRef, | ||||
| } from '@backstage-community/plugin-manage-module-tech-insights'; | ||||
| import { createManageApiFactory } from '@backstage-community/plugin-manage-react'; | ||||
| 
 | ||||
| export const apis: AnyApiFactory[] = [ | ||||
|   createApiFactory({ | ||||
|     api: scmIntegrationsApiRef, | ||||
|     deps: { configApi: configApiRef }, | ||||
|     factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), | ||||
|   }), | ||||
|   ScmAuth.createDefaultApiFactory(), | ||||
|   createManageTechInsightsApiFactory(), | ||||
|   createManageApiFactory({ | ||||
|     kindOrder: ['component', 'api', 'template', 'system'], | ||||
|     extensions: [manageTechInsightsApiRef], | ||||
|   }), | ||||
| ]; | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -0,0 +1,46 @@ | |||
| /* | ||||
|  * Copyright 2024 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 React from 'react'; | ||||
| import { makeStyles } from '@material-ui/core'; | ||||
| 
 | ||||
| const useStyles = makeStyles({ | ||||
|   svg: { | ||||
|     width: 'auto', | ||||
|     height: 28, | ||||
|   }, | ||||
|   path: { | ||||
|     fill: '#7df3e1', | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| const LogoIcon = () => { | ||||
|   const classes = useStyles(); | ||||
| 
 | ||||
|   return ( | ||||
|     <svg | ||||
|       className={classes.svg} | ||||
|       xmlns="http://www.w3.org/2000/svg" | ||||
|       viewBox="0 0 337.46 428.5" | ||||
|     > | ||||
|       <path | ||||
|         className={classes.path} | ||||
|         d="M303,166.05a80.69,80.69,0,0,0,13.45-10.37c.79-.77,1.55-1.53,2.3-2.3a83.12,83.12,0,0,0,7.93-9.38A63.69,63.69,0,0,0,333,133.23a48.58,48.58,0,0,0,4.35-16.4c1.49-19.39-10-38.67-35.62-54.22L198.56,0,78.3,115.23,0,190.25l108.6,65.91a111.59,111.59,0,0,0,57.76,16.41c24.92,0,48.8-8.8,66.42-25.69,19.16-18.36,25.52-42.12,13.7-61.87a49.22,49.22,0,0,0-6.8-8.87A89.17,89.17,0,0,0,259,178.29h.15a85.08,85.08,0,0,0,31-5.79A80.88,80.88,0,0,0,303,166.05ZM202.45,225.86c-19.32,18.51-50.4,21.23-75.7,5.9L51.61,186.15l67.45-64.64,76.41,46.38C223,184.58,221.49,207.61,202.45,225.86Zm8.93-82.22-70.65-42.89L205.14,39,274.51,81.1c25.94,15.72,29.31,37,10.55,55A60.69,60.69,0,0,1,211.38,143.64Zm29.86,190c-19.57,18.75-46.17,29.09-74.88,29.09a123.73,123.73,0,0,1-64.1-18.2L0,282.52v24.67L108.6,373.1a111.6,111.6,0,0,0,57.76,16.42c24.92,0,48.8-8.81,66.42-25.69,12.88-12.34,20-27.13,19.68-41.49v-1.79A87.27,87.27,0,0,1,241.24,333.68Zm0-39c-19.57,18.75-46.17,29.08-74.88,29.08a123.81,123.81,0,0,1-64.1-18.19L0,243.53v24.68l108.6,65.91a111.6,111.6,0,0,0,57.76,16.42c24.92,0,48.8-8.81,66.42-25.69,12.88-12.34,20-27.13,19.68-41.5v-1.78A87.27,87.27,0,0,1,241.24,294.7Zm0-39c-19.57,18.76-46.17,29.09-74.88,29.09a123.81,123.81,0,0,1-64.1-18.19L0,204.55v24.68l108.6,65.91a111.59,111.59,0,0,0,57.76,16.41c24.92,0,48.8-8.8,66.42-25.68,12.88-12.35,20-27.13,19.68-41.5v-1.82A86.09,86.09,0,0,1,241.24,255.71Zm83.7,25.74a94.15,94.15,0,0,1-60.2,25.86h0V334a81.6,81.6,0,0,0,51.74-22.37c14-13.38,21.14-28.11,21-42.64v-2.19A94.92,94.92,0,0,1,324.94,281.45Zm-83.7,91.21c-19.57,18.76-46.17,29.09-74.88,29.09a123.73,123.73,0,0,1-64.1-18.2L0,321.5v24.68l108.6,65.9a111.6,111.6,0,0,0,57.76,16.42c24.92,0,48.8-8.8,66.42-25.69,12.88-12.34,20-27.13,19.68-41.49v-1.79A86.29,86.29,0,0,1,241.24,372.66ZM327,162.45c-.68.69-1.35,1.38-2.05,2.06a94.37,94.37,0,0,1-10.64,8.65,91.35,91.35,0,0,1-11.6,7,94.53,94.53,0,0,1-26.24,8.71,97.69,97.69,0,0,1-14.16,1.57c.5,1.61.9,3.25,1.25,4.9a53.27,53.27,0,0,1,1.14,12V217h.05a84.41,84.41,0,0,0,25.35-5.55,81,81,0,0,0,26.39-16.82c.8-.77,1.5-1.56,2.26-2.34a82.08,82.08,0,0,0,7.93-9.38A63.76,63.76,0,0,0,333,172.17a48.55,48.55,0,0,0,4.32-16.45c.09-1.23.2-2.47.19-3.7V150q-1.08,1.54-2.25,3.09A96.73,96.73,0,0,1,327,162.45Zm0,77.92c-.69.7-1.31,1.41-2,2.1a94.2,94.2,0,0,1-60.2,25.86h0l0,26.67h0a81.6,81.6,0,0,0,51.74-22.37A73.51,73.51,0,0,0,333,250.13a48.56,48.56,0,0,0,4.32-16.44c.09-1.24.2-2.47.19-3.71v-2.19c-.74,1.07-1.46,2.15-2.27,3.21A95.68,95.68,0,0,1,327,240.37Zm0-39c-.69.7-1.31,1.41-2,2.1a93.18,93.18,0,0,1-10.63,8.65,91.63,91.63,0,0,1-11.63,7,95.47,95.47,0,0,1-37.94,10.18h0V256h0a81.65,81.65,0,0,0,51.74-22.37c.8-.77,1.5-1.56,2.26-2.34a82.08,82.08,0,0,0,7.93-9.38A63.76,63.76,0,0,0,333,211.15a48.56,48.56,0,0,0,4.32-16.44c.09-1.24.2-2.48.19-3.71v-2.2c-.74,1.08-1.46,2.16-2.27,3.22A95.68,95.68,0,0,1,327,201.39Z" | ||||
|       /> | ||||
|     </svg> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export default LogoIcon; | ||||
|  | @ -0,0 +1,130 @@ | |||
| /* | ||||
|  * Copyright 2024 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 React, { PropsWithChildren } from 'react'; | ||||
| import { makeStyles } from '@material-ui/core'; | ||||
| import CategoryIcon from '@material-ui/icons/Category'; | ||||
| import HomeIcon from '@material-ui/icons/Home'; | ||||
| import ExtensionIcon from '@material-ui/icons/Extension'; | ||||
| import MapIcon from '@material-ui/icons/MyLocation'; | ||||
| import LibraryBooks from '@material-ui/icons/LibraryBooks'; | ||||
| import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; | ||||
| import LogoFull from './LogoFull'; | ||||
| import LogoIcon from './LogoIcon'; | ||||
| import { | ||||
|   Settings as SidebarSettings, | ||||
|   UserSettingsSignInAvatar, | ||||
| } from '@backstage/plugin-user-settings'; | ||||
| import { SidebarSearchModal } from '@backstage/plugin-search'; | ||||
| import { | ||||
|   Sidebar, | ||||
|   sidebarConfig, | ||||
|   SidebarDivider, | ||||
|   SidebarGroup, | ||||
|   SidebarItem, | ||||
|   SidebarPage, | ||||
|   SidebarScrollWrapper, | ||||
|   SidebarSpace, | ||||
|   useSidebarOpenState, | ||||
|   Link, | ||||
| } from '@backstage/core-components'; | ||||
| import { useRouteRef } from '@backstage/core-plugin-api'; | ||||
| import MenuIcon from '@material-ui/icons/Menu'; | ||||
| import SearchIcon from '@material-ui/icons/Search'; | ||||
| import EmojiObjectsIcon from '@material-ui/icons/EmojiObjects'; | ||||
| import ManageIcon from '@material-ui/icons/Ballot'; | ||||
| import { managePlugin } from '@backstage-community/plugin-manage'; | ||||
| 
 | ||||
| const useSidebarLogoStyles = makeStyles({ | ||||
|   root: { | ||||
|     width: sidebarConfig.drawerWidthClosed, | ||||
|     height: 3 * sidebarConfig.logoHeight, | ||||
|     display: 'flex', | ||||
|     flexFlow: 'row nowrap', | ||||
|     alignItems: 'center', | ||||
|     marginBottom: -14, | ||||
|   }, | ||||
|   link: { | ||||
|     width: sidebarConfig.drawerWidthClosed, | ||||
|     marginLeft: 24, | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| const SidebarLogo = () => { | ||||
|   const classes = useSidebarLogoStyles(); | ||||
|   const { isOpen } = useSidebarOpenState(); | ||||
| 
 | ||||
|   return ( | ||||
|     <div className={classes.root}> | ||||
|       <Link to="/" underline="none" className={classes.link} aria-label="Home"> | ||||
|         {isOpen ? <LogoFull /> : <LogoIcon />} | ||||
|       </Link> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export const Root = ({ children }: PropsWithChildren<{}>) => { | ||||
|   const managePage = useRouteRef(managePlugin.routes.root); | ||||
| 
 | ||||
|   return ( | ||||
|     <SidebarPage> | ||||
|       <Sidebar> | ||||
|         <SidebarLogo /> | ||||
|         <SidebarGroup label="Search" icon={<SearchIcon />} to="/search"> | ||||
|           <SidebarSearchModal /> | ||||
|         </SidebarGroup> | ||||
|         <SidebarDivider /> | ||||
|         <SidebarGroup label="Menu" icon={<MenuIcon />}> | ||||
|           {/* Global nav, not org-specific */} | ||||
|           <SidebarItem icon={HomeIcon} to="catalog" text="Home" /> | ||||
|           <SidebarItem | ||||
|             icon={ManageIcon} | ||||
|             to={managePage()} | ||||
|             text="Manage" | ||||
|             data-tour="manage" | ||||
|           /> | ||||
|           <SidebarItem icon={CategoryIcon} to="catalog" text="Catalog" /> | ||||
|           <SidebarItem icon={ExtensionIcon} to="api-docs" text="APIs" /> | ||||
|           <SidebarItem icon={LibraryBooks} to="docs" text="Docs" /> | ||||
|           <SidebarItem | ||||
|             icon={CreateComponentIcon} | ||||
|             to="create" | ||||
|             text="Create..." | ||||
|           /> | ||||
|           {/* End global nav */} | ||||
|           <SidebarDivider /> | ||||
|           <SidebarScrollWrapper> | ||||
|             <SidebarItem icon={MapIcon} to="tech-radar" text="Tech Radar" /> | ||||
|             <SidebarItem | ||||
|               icon={EmojiObjectsIcon} | ||||
|               to="tech-insights" | ||||
|               text="Tech insight" | ||||
|             /> | ||||
|           </SidebarScrollWrapper> | ||||
|         </SidebarGroup> | ||||
|         <SidebarSpace /> | ||||
|         <SidebarDivider /> | ||||
|         <SidebarGroup | ||||
|           label="Settings" | ||||
|           icon={<UserSettingsSignInAvatar />} | ||||
|           to="/settings" | ||||
|         > | ||||
|           <SidebarSettings /> | ||||
|         </SidebarGroup> | ||||
|       </Sidebar> | ||||
|       {children} | ||||
|     </SidebarPage> | ||||
|   ); | ||||
| }; | ||||
|  | @ -0,0 +1,16 @@ | |||
| /* | ||||
|  * Copyright 2024 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. | ||||
|  */ | ||||
| export { Root } from './Root'; | ||||
|  | @ -0,0 +1,414 @@ | |||
| /* | ||||
|  * Copyright 2024 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 React from 'react'; | ||||
| import { Button, Grid } from '@material-ui/core'; | ||||
| import { | ||||
|   EntityApiDefinitionCard, | ||||
|   EntityConsumedApisCard, | ||||
|   EntityConsumingComponentsCard, | ||||
|   EntityHasApisCard, | ||||
|   EntityProvidedApisCard, | ||||
|   EntityProvidingComponentsCard, | ||||
| } from '@backstage/plugin-api-docs'; | ||||
| import { | ||||
|   EntityAboutCard, | ||||
|   EntityDependsOnComponentsCard, | ||||
|   EntityDependsOnResourcesCard, | ||||
|   EntityHasComponentsCard, | ||||
|   EntityHasResourcesCard, | ||||
|   EntityHasSubcomponentsCard, | ||||
|   EntityHasSystemsCard, | ||||
|   EntityLayout, | ||||
|   EntityLinksCard, | ||||
|   EntitySwitch, | ||||
|   EntityOrphanWarning, | ||||
|   EntityProcessingErrorsPanel, | ||||
|   isComponentType, | ||||
|   isKind, | ||||
|   hasCatalogProcessingErrors, | ||||
|   isOrphan, | ||||
|   hasRelationWarnings, | ||||
|   EntityRelationWarning, | ||||
| } from '@backstage/plugin-catalog'; | ||||
| import { | ||||
|   isGithubActionsAvailable, | ||||
|   EntityGithubActionsContent, | ||||
| } from '@backstage-community/plugin-github-actions'; | ||||
| import { | ||||
|   EntityUserProfileCard, | ||||
|   EntityGroupProfileCard, | ||||
|   EntityMembersListCard, | ||||
|   EntityOwnershipCard, | ||||
| } from '@backstage/plugin-org'; | ||||
| import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; | ||||
| import { EmptyState } from '@backstage/core-components'; | ||||
| import { | ||||
|   Direction, | ||||
|   EntityCatalogGraphCard, | ||||
| } from '@backstage/plugin-catalog-graph'; | ||||
| import { | ||||
|   RELATION_API_CONSUMED_BY, | ||||
|   RELATION_API_PROVIDED_BY, | ||||
|   RELATION_CONSUMES_API, | ||||
|   RELATION_DEPENDENCY_OF, | ||||
|   RELATION_DEPENDS_ON, | ||||
|   RELATION_HAS_PART, | ||||
|   RELATION_PART_OF, | ||||
|   RELATION_PROVIDES_API, | ||||
| } from '@backstage/catalog-model'; | ||||
| 
 | ||||
| import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; | ||||
| import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; | ||||
| import { EntityTechInsightsScorecardCard } from '@backstage-community/plugin-tech-insights'; | ||||
| 
 | ||||
| const techdocsContent = ( | ||||
|   <EntityTechdocsContent> | ||||
|     <TechDocsAddons> | ||||
|       <ReportIssue /> | ||||
|     </TechDocsAddons> | ||||
|   </EntityTechdocsContent> | ||||
| ); | ||||
| 
 | ||||
| const cicdContent = ( | ||||
|   // This is an example of how you can implement your company's logic in entity page.
 | ||||
|   // You can for example enforce that all components of type 'service' should use GitHubActions
 | ||||
|   <EntitySwitch> | ||||
|     <EntitySwitch.Case if={isGithubActionsAvailable}> | ||||
|       <EntityGithubActionsContent /> | ||||
|     </EntitySwitch.Case> | ||||
| 
 | ||||
|     <EntitySwitch.Case> | ||||
|       <EmptyState | ||||
|         title="No CI/CD available for this entity" | ||||
|         missing="info" | ||||
|         description="You need to add an annotation to your component if you want to enable CI/CD for it. You can read more about annotations in Backstage by clicking the button below." | ||||
|         action={ | ||||
|           <Button | ||||
|             variant="contained" | ||||
|             color="primary" | ||||
|             href="https://backstage.io/docs/features/software-catalog/well-known-annotations" | ||||
|           > | ||||
|             Read more | ||||
|           </Button> | ||||
|         } | ||||
|       /> | ||||
|     </EntitySwitch.Case> | ||||
|   </EntitySwitch> | ||||
| ); | ||||
| 
 | ||||
| const entityWarningContent = ( | ||||
|   <> | ||||
|     <EntitySwitch> | ||||
|       <EntitySwitch.Case if={isOrphan}> | ||||
|         <Grid item xs={12}> | ||||
|           <EntityOrphanWarning /> | ||||
|         </Grid> | ||||
|       </EntitySwitch.Case> | ||||
|     </EntitySwitch> | ||||
| 
 | ||||
|     <EntitySwitch> | ||||
|       <EntitySwitch.Case if={hasRelationWarnings}> | ||||
|         <Grid item xs={12}> | ||||
|           <EntityRelationWarning /> | ||||
|         </Grid> | ||||
|       </EntitySwitch.Case> | ||||
|     </EntitySwitch> | ||||
| 
 | ||||
|     <EntitySwitch> | ||||
|       <EntitySwitch.Case if={hasCatalogProcessingErrors}> | ||||
|         <Grid item xs={12}> | ||||
|           <EntityProcessingErrorsPanel /> | ||||
|         </Grid> | ||||
|       </EntitySwitch.Case> | ||||
|     </EntitySwitch> | ||||
|   </> | ||||
| ); | ||||
| 
 | ||||
| const overviewContent = ( | ||||
|   <Grid container spacing={3} alignItems="stretch"> | ||||
|     {entityWarningContent} | ||||
|     <Grid item md={6}> | ||||
|       <EntityAboutCard variant="gridItem" /> | ||||
|     </Grid> | ||||
|     <Grid item md={6} xs={12}> | ||||
|       <EntityCatalogGraphCard variant="gridItem" height={400} /> | ||||
|     </Grid> | ||||
| 
 | ||||
|     <Grid item md={6} xs={12}> | ||||
|       <EntityTechInsightsScorecardCard | ||||
|         title="Customized title for the scorecard" | ||||
|         description="Small description about scorecards" | ||||
|       /> | ||||
|     </Grid> | ||||
| 
 | ||||
|     <Grid item md={4} xs={12}> | ||||
|       <EntityLinksCard /> | ||||
|     </Grid> | ||||
|     <Grid item md={8} xs={12}> | ||||
|       <EntityHasSubcomponentsCard variant="gridItem" /> | ||||
|     </Grid> | ||||
|   </Grid> | ||||
| ); | ||||
| 
 | ||||
| const serviceEntityPage = ( | ||||
|   <EntityLayout> | ||||
|     <EntityLayout.Route path="/" title="Overview"> | ||||
|       {overviewContent} | ||||
|     </EntityLayout.Route> | ||||
| 
 | ||||
|     <EntityLayout.Route path="/ci-cd" title="CI/CD"> | ||||
|       {cicdContent} | ||||
|     </EntityLayout.Route> | ||||
| 
 | ||||
|     <EntityLayout.Route path="/api" title="API"> | ||||
|       <Grid container spacing={3} alignItems="stretch"> | ||||
|         <Grid item md={6}> | ||||
|           <EntityProvidedApisCard /> | ||||
|         </Grid> | ||||
|         <Grid item md={6}> | ||||
|           <EntityConsumedApisCard /> | ||||
|         </Grid> | ||||
|       </Grid> | ||||
|     </EntityLayout.Route> | ||||
| 
 | ||||
|     <EntityLayout.Route path="/dependencies" title="Dependencies"> | ||||
|       <Grid container spacing={3} alignItems="stretch"> | ||||
|         <Grid item md={6}> | ||||
|           <EntityDependsOnComponentsCard variant="gridItem" /> | ||||
|         </Grid> | ||||
|         <Grid item md={6}> | ||||
|           <EntityDependsOnResourcesCard variant="gridItem" /> | ||||
|         </Grid> | ||||
|       </Grid> | ||||
|     </EntityLayout.Route> | ||||
| 
 | ||||
|     <EntityLayout.Route path="/docs" title="Docs"> | ||||
|       {techdocsContent} | ||||
|     </EntityLayout.Route> | ||||
|   </EntityLayout> | ||||
| ); | ||||
| 
 | ||||
| const websiteEntityPage = ( | ||||
|   <EntityLayout> | ||||
|     <EntityLayout.Route path="/" title="Overview"> | ||||
|       {overviewContent} | ||||
|     </EntityLayout.Route> | ||||
| 
 | ||||
|     <EntityLayout.Route path="/ci-cd" title="CI/CD"> | ||||
|       {cicdContent} | ||||
|     </EntityLayout.Route> | ||||
| 
 | ||||
|     <EntityLayout.Route path="/dependencies" title="Dependencies"> | ||||
|       <Grid container spacing={3} alignItems="stretch"> | ||||
|         <Grid item md={6}> | ||||
|           <EntityDependsOnComponentsCard variant="gridItem" /> | ||||
|         </Grid> | ||||
|         <Grid item md={6}> | ||||
|           <EntityDependsOnResourcesCard variant="gridItem" /> | ||||
|         </Grid> | ||||
|       </Grid> | ||||
|     </EntityLayout.Route> | ||||
| 
 | ||||
|     <EntityLayout.Route path="/docs" title="Docs"> | ||||
|       {techdocsContent} | ||||
|     </EntityLayout.Route> | ||||
|   </EntityLayout> | ||||
| ); | ||||
| 
 | ||||
| /** | ||||
|  * NOTE: This page is designed to work on small screens such as mobile devices. | ||||
|  * This is based on Material UI Grid. If breakpoints are used, each grid item must set the `xs` prop to a column size or to `true`, | ||||
|  * since this does not default. If no breakpoints are used, the items will equitably share the available space. | ||||
|  * https://material-ui.com/components/grid/#basic-grid.
 | ||||
|  */ | ||||
| 
 | ||||
| const defaultEntityPage = ( | ||||
|   <EntityLayout> | ||||
|     <EntityLayout.Route path="/" title="Overview"> | ||||
|       {overviewContent} | ||||
|     </EntityLayout.Route> | ||||
| 
 | ||||
|     <EntityLayout.Route path="/docs" title="Docs"> | ||||
|       {techdocsContent} | ||||
|     </EntityLayout.Route> | ||||
|   </EntityLayout> | ||||
| ); | ||||
| 
 | ||||
| const componentPage = ( | ||||
|   <EntitySwitch> | ||||
|     <EntitySwitch.Case if={isComponentType('service')}> | ||||
|       {serviceEntityPage} | ||||
|     </EntitySwitch.Case> | ||||
| 
 | ||||
|     <EntitySwitch.Case if={isComponentType('website')}> | ||||
|       {websiteEntityPage} | ||||
|     </EntitySwitch.Case> | ||||
| 
 | ||||
|     <EntitySwitch.Case>{defaultEntityPage}</EntitySwitch.Case> | ||||
|   </EntitySwitch> | ||||
| ); | ||||
| 
 | ||||
| const apiPage = ( | ||||
|   <EntityLayout> | ||||
|     <EntityLayout.Route path="/" title="Overview"> | ||||
|       <Grid container spacing={3}> | ||||
|         {entityWarningContent} | ||||
|         <Grid item md={6}> | ||||
|           <EntityAboutCard /> | ||||
|         </Grid> | ||||
|         <Grid item md={6} xs={12}> | ||||
|           <EntityCatalogGraphCard variant="gridItem" height={400} /> | ||||
|         </Grid> | ||||
|         <Grid item md={4} xs={12}> | ||||
|           <EntityLinksCard /> | ||||
|         </Grid> | ||||
|         <Grid container item md={12}> | ||||
|           <Grid item md={6}> | ||||
|             <EntityProvidingComponentsCard /> | ||||
|           </Grid> | ||||
|           <Grid item md={6}> | ||||
|             <EntityConsumingComponentsCard /> | ||||
|           </Grid> | ||||
|         </Grid> | ||||
|       </Grid> | ||||
|     </EntityLayout.Route> | ||||
| 
 | ||||
|     <EntityLayout.Route path="/definition" title="Definition"> | ||||
|       <Grid container spacing={3}> | ||||
|         <Grid item xs={12}> | ||||
|           <EntityApiDefinitionCard /> | ||||
|         </Grid> | ||||
|       </Grid> | ||||
|     </EntityLayout.Route> | ||||
|   </EntityLayout> | ||||
| ); | ||||
| 
 | ||||
| const userPage = ( | ||||
|   <EntityLayout> | ||||
|     <EntityLayout.Route path="/" title="Overview"> | ||||
|       <Grid container spacing={3}> | ||||
|         {entityWarningContent} | ||||
|         <Grid item xs={12} md={6}> | ||||
|           <EntityUserProfileCard variant="gridItem" /> | ||||
|         </Grid> | ||||
|         <Grid item xs={12} md={6}> | ||||
|           <EntityOwnershipCard variant="gridItem" /> | ||||
|         </Grid> | ||||
|       </Grid> | ||||
|     </EntityLayout.Route> | ||||
|   </EntityLayout> | ||||
| ); | ||||
| 
 | ||||
| const groupPage = ( | ||||
|   <EntityLayout> | ||||
|     <EntityLayout.Route path="/" title="Overview"> | ||||
|       <Grid container spacing={3}> | ||||
|         {entityWarningContent} | ||||
|         <Grid item xs={12} md={6}> | ||||
|           <EntityGroupProfileCard variant="gridItem" /> | ||||
|         </Grid> | ||||
|         <Grid item xs={12} md={6}> | ||||
|           <EntityOwnershipCard variant="gridItem" /> | ||||
|         </Grid> | ||||
|         <Grid item xs={12} md={6}> | ||||
|           <EntityMembersListCard /> | ||||
|         </Grid> | ||||
|         <Grid item xs={12} md={6}> | ||||
|           <EntityLinksCard /> | ||||
|         </Grid> | ||||
|       </Grid> | ||||
|     </EntityLayout.Route> | ||||
|   </EntityLayout> | ||||
| ); | ||||
| 
 | ||||
| const systemPage = ( | ||||
|   <EntityLayout> | ||||
|     <EntityLayout.Route path="/" title="Overview"> | ||||
|       <Grid container spacing={3} alignItems="stretch"> | ||||
|         {entityWarningContent} | ||||
|         <Grid item md={6}> | ||||
|           <EntityAboutCard variant="gridItem" /> | ||||
|         </Grid> | ||||
|         <Grid item md={6} xs={12}> | ||||
|           <EntityCatalogGraphCard variant="gridItem" height={400} /> | ||||
|         </Grid> | ||||
|         <Grid item md={4} xs={12}> | ||||
|           <EntityLinksCard /> | ||||
|         </Grid> | ||||
|         <Grid item md={8}> | ||||
|           <EntityHasComponentsCard variant="gridItem" /> | ||||
|         </Grid> | ||||
|         <Grid item md={6}> | ||||
|           <EntityHasApisCard variant="gridItem" /> | ||||
|         </Grid> | ||||
|         <Grid item md={6}> | ||||
|           <EntityHasResourcesCard variant="gridItem" /> | ||||
|         </Grid> | ||||
|       </Grid> | ||||
|     </EntityLayout.Route> | ||||
|     <EntityLayout.Route path="/diagram" title="Diagram"> | ||||
|       <EntityCatalogGraphCard | ||||
|         variant="gridItem" | ||||
|         direction={Direction.TOP_BOTTOM} | ||||
|         title="System Diagram" | ||||
|         height={700} | ||||
|         relations={[ | ||||
|           RELATION_PART_OF, | ||||
|           RELATION_HAS_PART, | ||||
|           RELATION_API_CONSUMED_BY, | ||||
|           RELATION_API_PROVIDED_BY, | ||||
|           RELATION_CONSUMES_API, | ||||
|           RELATION_PROVIDES_API, | ||||
|           RELATION_DEPENDENCY_OF, | ||||
|           RELATION_DEPENDS_ON, | ||||
|         ]} | ||||
|         unidirectional={false} | ||||
|       /> | ||||
|     </EntityLayout.Route> | ||||
|   </EntityLayout> | ||||
| ); | ||||
| 
 | ||||
| const domainPage = ( | ||||
|   <EntityLayout> | ||||
|     <EntityLayout.Route path="/" title="Overview"> | ||||
|       <Grid container spacing={3} alignItems="stretch"> | ||||
|         {entityWarningContent} | ||||
|         <Grid item md={6}> | ||||
|           <EntityAboutCard variant="gridItem" /> | ||||
|         </Grid> | ||||
|         <Grid item md={6} xs={12}> | ||||
|           <EntityCatalogGraphCard variant="gridItem" height={400} /> | ||||
|         </Grid> | ||||
|         <Grid item md={6}> | ||||
|           <EntityHasSystemsCard variant="gridItem" /> | ||||
|         </Grid> | ||||
|       </Grid> | ||||
|     </EntityLayout.Route> | ||||
|   </EntityLayout> | ||||
| ); | ||||
| 
 | ||||
| export const entityPage = ( | ||||
|   <EntitySwitch> | ||||
|     <EntitySwitch.Case if={isKind('component')} children={componentPage} /> | ||||
|     <EntitySwitch.Case if={isKind('api')} children={apiPage} /> | ||||
|     <EntitySwitch.Case if={isKind('group')} children={groupPage} /> | ||||
|     <EntitySwitch.Case if={isKind('user')} children={userPage} /> | ||||
|     <EntitySwitch.Case if={isKind('system')} children={systemPage} /> | ||||
|     <EntitySwitch.Case if={isKind('domain')} children={domainPage} /> | ||||
| 
 | ||||
|     <EntitySwitch.Case>{defaultEntityPage}</EntitySwitch.Case> | ||||
|   </EntitySwitch> | ||||
| ); | ||||
|  | @ -0,0 +1,58 @@ | |||
| /* | ||||
|  * 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 React from 'react'; | ||||
| 
 | ||||
| import { | ||||
|   MANAGE_KIND_COMMON, | ||||
|   ManageTabs, | ||||
|   OrganizationGraph, | ||||
| } from '@backstage-community/plugin-manage'; | ||||
| import { | ||||
|   manageTechInsightsColumns, | ||||
|   ManageTechInsightsCards, | ||||
|   ManageTechInsightsGrid, | ||||
| } from '@backstage-community/plugin-manage-module-tech-insights'; | ||||
| 
 | ||||
| export function Manage() { | ||||
|   return ( | ||||
|     <ManageTabs | ||||
|       combined={{ | ||||
|         header: <ManageTechInsightsCards inAccordion />, | ||||
|         columns: [manageTechInsightsColumns({ combined: true })], | ||||
|       }} | ||||
|       starred={{ | ||||
|         header: <ManageTechInsightsGrid />, | ||||
|         columns: [manageTechInsightsColumns({ combined: true })], | ||||
|       }} | ||||
|       kinds={{ | ||||
|         [MANAGE_KIND_COMMON]: { | ||||
|           header: <ManageTechInsightsCards inAccordion />, | ||||
|           columns: [manageTechInsightsColumns()], | ||||
|         }, | ||||
|         component: { | ||||
|           columns: [manageTechInsightsColumns({ combined: true })], | ||||
|         }, | ||||
|       }} | ||||
|       tabsAfter={[ | ||||
|         { | ||||
|           path: 'organization', | ||||
|           title: 'Organization', | ||||
|           children: <OrganizationGraph />, | ||||
|         }, | ||||
|       ]} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,139 @@ | |||
| /* | ||||
|  * Copyright 2024 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 React from 'react'; | ||||
| import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; | ||||
| 
 | ||||
| import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; | ||||
| import { | ||||
|   catalogApiRef, | ||||
|   CATALOG_FILTER_EXISTS, | ||||
| } from '@backstage/plugin-catalog-react'; | ||||
| import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; | ||||
| 
 | ||||
| import { SearchType } from '@backstage/plugin-search'; | ||||
| import { | ||||
|   SearchBar, | ||||
|   SearchFilter, | ||||
|   SearchResult, | ||||
|   SearchPagination, | ||||
|   useSearch, | ||||
| } from '@backstage/plugin-search-react'; | ||||
| import { | ||||
|   CatalogIcon, | ||||
|   Content, | ||||
|   DocsIcon, | ||||
|   Header, | ||||
|   Page, | ||||
| } from '@backstage/core-components'; | ||||
| import { useApi } from '@backstage/core-plugin-api'; | ||||
| 
 | ||||
| const useStyles = makeStyles((theme: Theme) => ({ | ||||
|   bar: { | ||||
|     padding: theme.spacing(1, 0), | ||||
|   }, | ||||
|   filters: { | ||||
|     padding: theme.spacing(2), | ||||
|     marginTop: theme.spacing(2), | ||||
|   }, | ||||
|   filter: { | ||||
|     '& + &': { | ||||
|       marginTop: theme.spacing(2.5), | ||||
|     }, | ||||
|   }, | ||||
| })); | ||||
| 
 | ||||
| const SearchPage = () => { | ||||
|   const classes = useStyles(); | ||||
|   const { types } = useSearch(); | ||||
|   const catalogApi = useApi(catalogApiRef); | ||||
| 
 | ||||
|   return ( | ||||
|     <Page themeId="home"> | ||||
|       <Header title="Search" /> | ||||
|       <Content> | ||||
|         <Grid container direction="row"> | ||||
|           <Grid item xs={12}> | ||||
|             <Paper className={classes.bar}> | ||||
|               <SearchBar /> | ||||
|             </Paper> | ||||
|           </Grid> | ||||
|           <Grid item xs={3}> | ||||
|             <SearchType.Accordion | ||||
|               name="Result Type" | ||||
|               defaultValue="software-catalog" | ||||
|               types={[ | ||||
|                 { | ||||
|                   value: 'software-catalog', | ||||
|                   name: 'Software Catalog', | ||||
|                   icon: <CatalogIcon />, | ||||
|                 }, | ||||
|                 { | ||||
|                   value: 'techdocs', | ||||
|                   name: 'Documentation', | ||||
|                   icon: <DocsIcon />, | ||||
|                 }, | ||||
|               ]} | ||||
|             /> | ||||
|             <Paper className={classes.filters}> | ||||
|               {types.includes('techdocs') && ( | ||||
|                 <SearchFilter.Select | ||||
|                   className={classes.filter} | ||||
|                   label="Entity" | ||||
|                   name="name" | ||||
|                   values={async () => { | ||||
|                     // Return a list of entities which are documented.
 | ||||
|                     const { items } = await catalogApi.getEntities({ | ||||
|                       fields: ['metadata.name'], | ||||
|                       filter: { | ||||
|                         'metadata.annotations.backstage.io/techdocs-ref': | ||||
|                           CATALOG_FILTER_EXISTS, | ||||
|                       }, | ||||
|                     }); | ||||
| 
 | ||||
|                     const names = items.map(entity => entity.metadata.name); | ||||
|                     names.sort(); | ||||
|                     return names; | ||||
|                   }} | ||||
|                 /> | ||||
|               )} | ||||
|               <SearchFilter.Select | ||||
|                 className={classes.filter} | ||||
|                 label="Kind" | ||||
|                 name="kind" | ||||
|                 values={['Component', 'Template']} | ||||
|               /> | ||||
|               <SearchFilter.Checkbox | ||||
|                 className={classes.filter} | ||||
|                 label="Lifecycle" | ||||
|                 name="lifecycle" | ||||
|                 values={['experimental', 'production']} | ||||
|               /> | ||||
|             </Paper> | ||||
|           </Grid> | ||||
|           <Grid item xs={9}> | ||||
|             <SearchPagination /> | ||||
|             <SearchResult> | ||||
|               <CatalogSearchResultListItem icon={<CatalogIcon />} /> | ||||
|               <TechDocsSearchResultListItem icon={<DocsIcon />} /> | ||||
|             </SearchResult> | ||||
|           </Grid> | ||||
|         </Grid> | ||||
|       </Content> | ||||
|     </Page> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export const searchPage = <SearchPage />; | ||||
|  | @ -0,0 +1,21 @@ | |||
| /* | ||||
|  * Copyright 2024 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 '@backstage/cli/asset-types'; | ||||
| import React from 'react'; | ||||
| import ReactDOM from 'react-dom/client'; | ||||
| import App from './App'; | ||||
| 
 | ||||
| ReactDOM.createRoot(document.getElementById('root')!).render(<App />); | ||||
|  | @ -0,0 +1,16 @@ | |||
| /* | ||||
|  * Copyright 2024 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 '@testing-library/jest-dom'; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); | ||||
|  | @ -0,0 +1,107 @@ | |||
| # backend | ||||
| 
 | ||||
| ## 0.0.11 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [7a14237] | ||||
|   - @backstage-community/plugin-tech-insights-backend@1.2.2 | ||||
|   - @backstage-community/plugin-tech-insights-backend-module-jsonfc@0.1.59 | ||||
|   - @backstage-community/plugin-tech-insights-node@1.0.2 | ||||
|   - app@0.0.11 | ||||
| 
 | ||||
| ## 0.0.10 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [6a4787a] | ||||
|   - @backstage-community/plugin-tech-insights-backend@1.2.1 | ||||
| 
 | ||||
| ## 0.0.9 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [0f5c58a] | ||||
| - Updated dependencies [e516773] | ||||
| - Updated dependencies [e516773] | ||||
|   - @backstage-community/plugin-tech-insights-backend@1.2.0 | ||||
|   - @backstage-community/plugin-tech-insights-backend-module-jsonfc@0.1.58 | ||||
|   - @backstage-community/plugin-tech-insights-node@1.0.1 | ||||
|   - app@0.0.9 | ||||
| 
 | ||||
| ## 0.0.8 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [fad299b] | ||||
|   - @backstage-community/plugin-tech-insights-backend@1.1.0 | ||||
|   - app@0.0.8 | ||||
| 
 | ||||
| ## 0.0.7 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [9871d0b] | ||||
| - Updated dependencies [9871d0b] | ||||
|   - @backstage-community/plugin-tech-insights-node@1.0.0 | ||||
|   - @backstage-community/plugin-tech-insights-backend@1.0.0 | ||||
|   - @backstage-community/plugin-tech-insights-backend-module-jsonfc@0.1.57 | ||||
| 
 | ||||
| ## 0.0.6 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [1d33996] | ||||
|   - @backstage-community/plugin-tech-insights-backend-module-jsonfc@0.1.56 | ||||
|   - @backstage-community/plugin-tech-insights-node@0.6.7 | ||||
|   - app@0.0.7 | ||||
|   - @backstage-community/plugin-tech-insights-backend@0.6.3 | ||||
| 
 | ||||
| ## 0.0.5 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - @backstage-community/plugin-tech-insights-backend@0.6.2 | ||||
| - @backstage-community/plugin-tech-insights-backend-module-jsonfc@0.1.55 | ||||
| - @backstage-community/plugin-tech-insights-node@0.6.6 | ||||
| - app@0.0.6 | ||||
| 
 | ||||
| ## 0.0.4 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [ae2ee8a] | ||||
| - Updated dependencies [00d148d] | ||||
|   - @backstage-community/plugin-tech-insights-backend@0.6.1 | ||||
|   - @backstage-community/plugin-tech-insights-backend-module-jsonfc@0.1.54 | ||||
|   - @backstage-community/plugin-tech-insights-node@0.6.5 | ||||
|   - app@0.0.5 | ||||
| 
 | ||||
| ## 0.0.3 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [e49b4eb] | ||||
|   - @backstage-community/plugin-tech-insights-backend@0.6.0 | ||||
|   - app@0.0.3 | ||||
| 
 | ||||
| ## 0.0.2 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [7ac338c] | ||||
| - Updated dependencies [a8d8d44] | ||||
| - Updated dependencies [794cc8b] | ||||
|   - @backstage-community/plugin-tech-insights-backend-module-jsonfc@0.1.53 | ||||
|   - @backstage-community/plugin-tech-insights-backend@0.5.35 | ||||
|   - @backstage-community/plugin-tech-insights-node@0.6.4 | ||||
|   - app@0.0.2 | ||||
| 
 | ||||
| ## 0.0.1 | ||||
| 
 | ||||
| ### Patch Changes | ||||
| 
 | ||||
| - Updated dependencies [cbad35a] | ||||
|   - @backstage-community/plugin-tech-insights-backend-module-jsonfc@0.1.52 | ||||
|   - @backstage-community/plugin-tech-insights-backend@0.5.34 | ||||
|   - app@0.0.1 | ||||
|  | @ -0,0 +1,59 @@ | |||
| # example-backend | ||||
| 
 | ||||
| This package is an EXAMPLE of a Backstage backend. | ||||
| 
 | ||||
| The main purpose of this package is to provide a test bed for Backstage plugins | ||||
| that have a backend part. Feel free to experiment locally or within your fork by | ||||
| adding dependencies and routes to this backend, to try things out. | ||||
| 
 | ||||
| Our goal is to eventually amend the create-app flow of the CLI, such that a | ||||
| production ready version of a backend skeleton is made alongside the frontend | ||||
| app. Until then, feel free to experiment here! | ||||
| 
 | ||||
| ## Development | ||||
| 
 | ||||
| To run the example backend, first go to the project root and run | ||||
| 
 | ||||
| ```bash | ||||
| yarn install | ||||
| ``` | ||||
| 
 | ||||
| You should only need to do this once. | ||||
| 
 | ||||
| After that, go to the `packages/backend` directory and run | ||||
| 
 | ||||
| ```bash | ||||
| yarn start | ||||
| ``` | ||||
| 
 | ||||
| If you want to override any configuration locally, for example adding any secrets, | ||||
| you can do so in `app-config.local.yaml`. | ||||
| 
 | ||||
| The backend starts up on port 7007 per default. | ||||
| 
 | ||||
| ## Populating The Catalog | ||||
| 
 | ||||
| If you want to use the catalog functionality, you need to add so called | ||||
| locations to the backend. These are places where the backend can find some | ||||
| entity descriptor data to consume and serve. For more information, see | ||||
| [Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). | ||||
| 
 | ||||
| To get started quickly, this template already includes some statically configured example locations | ||||
| in `app-config.yaml` under `catalog.locations`. You can remove and replace these locations as you | ||||
| like, and also override them for local development in `app-config.local.yaml`. | ||||
| 
 | ||||
| ## Authentication | ||||
| 
 | ||||
| We chose [Passport](http://www.passportjs.org/) as authentication platform due | ||||
| to its comprehensive set of supported authentication | ||||
| [strategies](http://www.passportjs.org/packages/). | ||||
| 
 | ||||
| Read more about the | ||||
| [auth-backend](https://github.com/backstage/backstage/blob/master/plugins/auth-backend/README.md) | ||||
| and | ||||
| [how to add a new provider](https://github.com/backstage/backstage/blob/master/docs/auth/add-auth-provider.md) | ||||
| 
 | ||||
| ## Documentation | ||||
| 
 | ||||
| - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) | ||||
| - [Backstage Documentation](https://backstage.io/docs) | ||||
|  | @ -0,0 +1,31 @@ | |||
| # Knip report | ||||
| 
 | ||||
| ## Unused dependencies (17) | ||||
| 
 | ||||
| | Name                                                            | Location     | Severity | | ||||
| | :-------------------------------------------------------------- | :----------- | :------- | | ||||
| | @backstage-community/plugin-tech-insights-backend-module-jsonfc | package.json | error    | | ||||
| | @backstage/plugin-auth-backend-module-github-provider           | package.json | error    | | ||||
| | @backstage-community/plugin-tech-insights-backend               | package.json | error    | | ||||
| | @backstage-community/plugin-tech-insights-node                  | package.json | error    | | ||||
| | @backstage/plugin-search-backend-node                           | package.json | error    | | ||||
| | @backstage/plugin-permission-common                             | package.json | error    | | ||||
| | @backstage/plugin-permission-node                               | package.json | error    | | ||||
| | @backstage/plugin-auth-node                                     | package.json | error    | | ||||
| | @backstage/backend-common                                       | package.json | error    | | ||||
| | @backstage/config                                               | package.json | error    | | ||||
| | better-sqlite3                                                  | package.json | error    | | ||||
| | dockerode                                                       | package.json | error    | | ||||
| | node-gyp                                                        | package.json | error    | | ||||
| | winston                                                         | package.json | error    | | ||||
| | app                                                             | package.json | error    | | ||||
| | pg                                                              | package.json | error    | | ||||
| 
 | ||||
| ## Unused devDependencies (4) | ||||
| 
 | ||||
| | Name                             | Location     | Severity | | ||||
| | :------------------------------- | :----------- | :------- | | ||||
| | @types/express-serve-static-core | package.json | error    | | ||||
| | @types/dockerode                 | package.json | error    | | ||||
| | @types/express                   | package.json | error    | | ||||
| | @types/luxon                     | package.json | error    | | ||||
|  | @ -0,0 +1,66 @@ | |||
| { | ||||
|   "name": "backend", | ||||
|   "version": "0.0.11", | ||||
|   "main": "dist/index.cjs.js", | ||||
|   "types": "src/index.ts", | ||||
|   "private": true, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "https://github.com/backstage/community-plugins", | ||||
|     "directory": "workspaces/manage/packages/backend" | ||||
|   }, | ||||
|   "backstage": { | ||||
|     "role": "backend" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "start": "backstage-cli package start", | ||||
|     "build": "backstage-cli package build", | ||||
|     "lint": "backstage-cli package lint", | ||||
|     "test": "backstage-cli package test", | ||||
|     "clean": "backstage-cli package clean", | ||||
|     "build-image": "docker build ../.. -f Dockerfile --tag backstage" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@backstage-community/plugin-tech-insights-backend": "^2.1.1", | ||||
|     "@backstage-community/plugin-tech-insights-backend-module-jsonfc": "^0.3.1", | ||||
|     "@backstage-community/plugin-tech-insights-common": "^0.4.0", | ||||
|     "@backstage-community/plugin-tech-insights-node": "^2.2.0", | ||||
|     "@backstage/backend-defaults": "^0.6.2", | ||||
|     "@backstage/backend-plugin-api": "^1.1.1", | ||||
|     "@backstage/catalog-client": "^1.9.1", | ||||
|     "@backstage/catalog-model": "^1.7.3", | ||||
|     "@backstage/config": "^1.3.2", | ||||
|     "@backstage/plugin-app-backend": "^0.4.4", | ||||
|     "@backstage/plugin-auth-backend": "^0.24.2", | ||||
|     "@backstage/plugin-auth-backend-module-github-provider": "^0.2.4", | ||||
|     "@backstage/plugin-auth-backend-module-guest-provider": "^0.2.4", | ||||
|     "@backstage/plugin-auth-node": "^0.5.6", | ||||
|     "@backstage/plugin-catalog-backend": "^1.30.0", | ||||
|     "@backstage/plugin-permission-backend": "^0.5.53", | ||||
|     "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.2.4", | ||||
|     "@backstage/plugin-permission-common": "^0.8.4", | ||||
|     "@backstage/plugin-permission-node": "^0.8.7", | ||||
|     "@backstage/plugin-proxy-backend": "^0.5.10", | ||||
|     "@backstage/plugin-search-backend": "^1.8.0", | ||||
|     "@backstage/plugin-search-backend-module-catalog": "^0.3.0", | ||||
|     "@backstage/plugin-search-backend-module-techdocs": "^0.3.5", | ||||
|     "@backstage/plugin-search-backend-node": "^1.3.7", | ||||
|     "@backstage/plugin-techdocs-backend": "^1.11.5", | ||||
|     "app": "link:../app", | ||||
|     "better-sqlite3": "^9.0.0", | ||||
|     "dockerode": "^3.3.1", | ||||
|     "node-gyp": "^11.0.0", | ||||
|     "pg": "^8.11.3", | ||||
|     "winston": "^3.2.1" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@backstage/cli": "^0.29.5", | ||||
|     "@types/dockerode": "^3.3.0", | ||||
|     "@types/express": "^4.17.6", | ||||
|     "@types/express-serve-static-core": "^4.17.5", | ||||
|     "@types/luxon": "^2.0.4" | ||||
|   }, | ||||
|   "files": [ | ||||
|     "dist" | ||||
|   ] | ||||
| } | ||||
|  | @ -0,0 +1,49 @@ | |||
| /* | ||||
|  * Copyright 2024 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 { createBackend } from '@backstage/backend-defaults'; | ||||
| 
 | ||||
| const backend = createBackend(); | ||||
| 
 | ||||
| backend.add(import('@backstage/plugin-app-backend')); | ||||
| backend.add(import('@backstage/plugin-proxy-backend')); | ||||
| backend.add(import('@backstage/plugin-techdocs-backend')); | ||||
| 
 | ||||
| // auth plugin
 | ||||
| backend.add(import('@backstage/plugin-auth-backend')); | ||||
| // See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
 | ||||
| backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); | ||||
| // See https://backstage.io/docs/auth/guest/provider
 | ||||
| 
 | ||||
| // catalog plugin
 | ||||
| backend.add(import('@backstage/plugin-catalog-backend')); | ||||
| 
 | ||||
| // permission plugin
 | ||||
| backend.add(import('@backstage/plugin-permission-backend')); | ||||
| backend.add( | ||||
|   import('@backstage/plugin-permission-backend-module-allow-all-policy'), | ||||
| ); | ||||
| 
 | ||||
| // search plugin
 | ||||
| backend.add(import('@backstage/plugin-search-backend')); | ||||
| backend.add(import('@backstage/plugin-search-backend-module-catalog')); | ||||
| backend.add(import('@backstage/plugin-search-backend-module-techdocs')); | ||||
| 
 | ||||
| // tech insights
 | ||||
| backend.add(import('@backstage-community/plugin-tech-insights-backend')); | ||||
| backend.add(import('./plugins/tech-insights')); | ||||
| 
 | ||||
| backend.start(); | ||||
|  | @ -0,0 +1,206 @@ | |||
| /* | ||||
|  * Copyright 2024 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 { | ||||
|   coreServices, | ||||
|   createBackendModule, | ||||
| } from '@backstage/backend-plugin-api'; | ||||
| import { CatalogClient } from '@backstage/catalog-client'; | ||||
| import { Entity, ApiEntityV1alpha1 } from '@backstage/catalog-model'; | ||||
| 
 | ||||
| import { | ||||
|   JSON_RULE_ENGINE_CHECK_TYPE, | ||||
|   JsonRulesEngineFactCheckerFactory, | ||||
| } from '@backstage-community/plugin-tech-insights-backend-module-jsonfc'; | ||||
| import { | ||||
|   FactRetriever, | ||||
|   FactRetrieverContext, | ||||
|   techInsightsFactCheckerFactoryExtensionPoint, | ||||
|   techInsightsFactRetrieversExtensionPoint, | ||||
| } from '@backstage-community/plugin-tech-insights-node'; | ||||
| import { | ||||
|   entityMetadataFactRetriever, | ||||
|   techdocsFactRetriever, | ||||
| } from '@backstage-community/plugin-tech-insights-backend'; | ||||
| 
 | ||||
| export const checks = [ | ||||
|   { | ||||
|     id: 'groupOwnerCheck', | ||||
|     type: JSON_RULE_ENGINE_CHECK_TYPE, | ||||
|     name: 'Group Owner Check', | ||||
|     description: | ||||
|       'Verifies that a Group has been set as the owner for this entity', | ||||
|     factIds: ['entityOwnershipFactRetriever'], | ||||
|     rule: { | ||||
|       conditions: { | ||||
|         all: [ | ||||
|           { | ||||
|             fact: 'hasGroupOwner', | ||||
|             operator: 'equal', | ||||
|             value: true, | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|   { | ||||
|     id: 'titleCheck', | ||||
|     type: JSON_RULE_ENGINE_CHECK_TYPE, | ||||
|     name: 'Title Check', | ||||
|     description: 'Verifies that an entity has a title', | ||||
|     factIds: ['entityMetadataFactRetriever'], | ||||
|     rule: { | ||||
|       conditions: { | ||||
|         all: [ | ||||
|           { | ||||
|             fact: 'hasTitle', | ||||
|             operator: 'equal', | ||||
|             value: true, | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|   { | ||||
|     id: 'descriptionCheck', | ||||
|     type: JSON_RULE_ENGINE_CHECK_TYPE, | ||||
|     name: 'Description Check', | ||||
|     description: 'Verifies that an entity has a description', | ||||
|     factIds: ['entityMetadataFactRetriever'], | ||||
|     rule: { | ||||
|       conditions: { | ||||
|         all: [ | ||||
|           { | ||||
|             fact: 'hasDescription', | ||||
|             operator: 'equal', | ||||
|             value: true, | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|   { | ||||
|     id: 'apiDefinitionCheck', | ||||
|     type: JSON_RULE_ENGINE_CHECK_TYPE, | ||||
|     name: 'API definition Check', | ||||
|     description: 'Verifies that a API has a definition set', | ||||
|     factIds: ['apiDefinitionFactRetriever'], | ||||
|     rule: { | ||||
|       conditions: { | ||||
|         all: [ | ||||
|           { | ||||
|             fact: 'hasDefinition', | ||||
|             operator: 'equal', | ||||
|             value: true, | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| ]; | ||||
| 
 | ||||
| export const apiDefinitionFactRetriever: FactRetriever = { | ||||
|   id: 'apiDefinitionFactRetriever', | ||||
|   version: '0.0.1', | ||||
|   title: 'API Definition', | ||||
|   description: 'Generates facts which indicate the completeness of API spec', | ||||
|   schema: { | ||||
|     hasDefinition: { | ||||
|       type: 'boolean', | ||||
|       description: 'The entity has a definition in spec', | ||||
|     }, | ||||
|   }, | ||||
|   handler: async ({ discovery, auth }: FactRetrieverContext) => { | ||||
|     const { token } = await auth.getPluginRequestToken({ | ||||
|       onBehalfOf: await auth.getOwnServiceCredentials(), | ||||
|       targetPluginId: 'catalog', | ||||
|     }); | ||||
|     const catalogClient = new CatalogClient({ | ||||
|       discoveryApi: discovery, | ||||
|     }); | ||||
|     const entities = await catalogClient.getEntities( | ||||
|       { filter: { kind: ['API'] } }, | ||||
|       { token }, | ||||
|     ); | ||||
| 
 | ||||
|     return entities.items.map((entity: Entity) => { | ||||
|       return { | ||||
|         entity: { | ||||
|           namespace: entity.metadata.namespace!, | ||||
|           kind: entity.kind, | ||||
|           name: entity.metadata.name, | ||||
|         }, | ||||
|         facts: { | ||||
|           hasDefinition: | ||||
|             (entity as ApiEntityV1alpha1).spec?.definition && | ||||
|             (entity as ApiEntityV1alpha1).spec?.definition.length > 0, | ||||
|         }, | ||||
|       }; | ||||
|     }); | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| export default createBackendModule({ | ||||
|   pluginId: 'tech-insights', | ||||
|   moduleId: 'generic-fact-retrievers', | ||||
|   register(reg) { | ||||
|     reg.registerInit({ | ||||
|       deps: { | ||||
|         logger: coreServices.logger, | ||||
|         techInsightsFactCheckerFactory: | ||||
|           techInsightsFactCheckerFactoryExtensionPoint, | ||||
|         techInsightsFactRetrievers: techInsightsFactRetrieversExtensionPoint, | ||||
|       }, | ||||
|       async init({ | ||||
|         logger, | ||||
|         techInsightsFactCheckerFactory, | ||||
|         techInsightsFactRetrievers, | ||||
|       }) { | ||||
|         techInsightsFactCheckerFactory.setFactCheckerFactory( | ||||
|           new JsonRulesEngineFactCheckerFactory({ | ||||
|             logger, | ||||
|             checks, | ||||
|           }), | ||||
|         ); | ||||
| 
 | ||||
|         techInsightsFactRetrievers.addFactRetrievers({ | ||||
|           apiDefinitionFactRetriever, | ||||
|           entityMetadataFactRetriever: { | ||||
|             ...entityMetadataFactRetriever, | ||||
|             entityFilter: [ | ||||
|               { | ||||
|                 kind: [ | ||||
|                   'location', | ||||
|                   'domain', | ||||
|                   'system', | ||||
|                   'component', | ||||
|                   'api', | ||||
|                   'resource', | ||||
|                   'template', | ||||
|                 ], | ||||
|               }, | ||||
|             ], | ||||
|           }, | ||||
|           techdocsFactRetriever: { | ||||
|             ...techdocsFactRetriever, | ||||
|             entityFilter: [ | ||||
|               { kind: ['component', 'system', 'api', 'template'] }, | ||||
|             ], | ||||
|           }, | ||||
|         }); | ||||
|       }, | ||||
|     }); | ||||
|   }, | ||||
| }); | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); | ||||
|  | @ -0,0 +1,57 @@ | |||
| # Manage page extension: tech-insights | ||||
| 
 | ||||
| To add columns to the table of owned entities, showing tech insight checks, use the `manageTechInsightsColumns` function. To show gauges (e.g. above the tables), use `ManageTechInsightsCards` or `ManageTechInsightsGrid`. | ||||
| 
 | ||||
| First, ensure to install the tech-insight module by adding its API ref to `createManageApiFactory`: | ||||
| 
 | ||||
| ```ts | ||||
| import { | ||||
|   createManageTechInsightsApiFactory, | ||||
|   manageTechInsightsApiRef, | ||||
| } from '@backstage-community/plugin-manage-module-tech-insights'; | ||||
| 
 | ||||
| [ | ||||
|   // ..., | ||||
|   createManageTechInsightsApiFactory(), | ||||
|   createManageApiFactory({ | ||||
|     extensions: [manageTechInsightsApiRef], | ||||
|   }), | ||||
| ]; | ||||
| ``` | ||||
| 
 | ||||
| In `Manage.tsx` (as described in the README for `@backstage-community/plugin-manage`): | ||||
| 
 | ||||
| ```tsx | ||||
| import { | ||||
|   manageTechInsightsColumns, | ||||
|   ManageTechInsightsCards, | ||||
|   ManageTechInsightsGrid, | ||||
| } from '@backstage-community/plugin-manage-module-tech-insights'; | ||||
| 
 | ||||
| export function Manage() { | ||||
|   return ( | ||||
|     <ManageTabs | ||||
|       combined={{ | ||||
|         header: <ManageTechInsightsGrid inAccordion />, | ||||
|         columns: [manageTechInsightsColumns({ combined: true })], | ||||
|       }} | ||||
|       starred={{ | ||||
|         header: <ManageTechInsightsGrid inAccordion />, | ||||
|         columns: [manageTechInsightsColumns({ combined: true })], | ||||
|       }} | ||||
|       kinds={{ | ||||
|         [MANAGE_KIND_COMMON]: { | ||||
|           header: <ManageTechInsightsCards inAccordion />, | ||||
|           columns: [manageTechInsightsColumns()], | ||||
|         }, | ||||
|         component: { | ||||
|           // There are maybe too many tech-insights checks for components to | ||||
|           // show one per column. | ||||
|           // The `combined` option squeezes them into one column. | ||||
|           columns: [manageTechInsightsColumns({ combined: true })], | ||||
|         }, | ||||
|       }} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| ``` | ||||
|  | @ -0,0 +1,10 @@ | |||
| apiVersion: backstage.io/v1alpha1 | ||||
| kind: Component | ||||
| metadata: | ||||
|   name: backstage-plugin-manage-module-tech-insights | ||||
|   title: '@backstage/plugin-manage-module-tech-insights' | ||||
|   description: Manage page, Tech Insights module | ||||
| spec: | ||||
|   lifecycle: experimental | ||||
|   type: backstage-frontend-plugin | ||||
|   owner: maintainers | ||||
|  | @ -0,0 +1,68 @@ | |||
| { | ||||
|   "name": "@backstage-community/plugin-manage-module-tech-insights", | ||||
|   "description": "Manage plugin - Tech Insights module", | ||||
|   "version": "0.1.0", | ||||
|   "backstage": { | ||||
|     "role": "frontend-plugin-module", | ||||
|     "pluginId": "manage", | ||||
|     "pluginPackages": [ | ||||
|       "@backstage-community/plugin-manage-module-tech-insights" | ||||
|     ], | ||||
|     "pluginPackage": "@backstage-community/plugin-manage" | ||||
|   }, | ||||
|   "main": "src/index.ts", | ||||
|   "types": "src/index.ts", | ||||
|   "license": "Apache-2.0", | ||||
|   "publishConfig": { | ||||
|     "access": "public", | ||||
|     "main": "dist/index.esm.js", | ||||
|     "types": "dist/index.d.ts" | ||||
|   }, | ||||
|   "homepage": "https://backstage.io", | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "https://github.com/backstage/backstage", | ||||
|     "directory": "plugins/manage-module-tech-insights" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "backstage", | ||||
|     "manage", | ||||
|     "page", | ||||
|     "tech-insights", | ||||
|     "tech", | ||||
|     "insights" | ||||
|   ], | ||||
|   "sideEffects": false, | ||||
|   "scripts": { | ||||
|     "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-community/plugin-manage-react": "workspace:^", | ||||
|     "@backstage-community/plugin-tech-insights": "^0.4.0", | ||||
|     "@backstage-community/plugin-tech-insights-common": "^0.4.0", | ||||
|     "@backstage/catalog-model": "^1.7.3", | ||||
|     "@backstage/core-components": "^0.16.3", | ||||
|     "@backstage/core-plugin-api": "^1.10.3", | ||||
|     "@backstage/plugin-catalog-react": "^1.15.1", | ||||
|     "@mui/material": "^5.15.16", | ||||
|     "@mui/styles": "^5", | ||||
|     "react-use": "^17.5.0" | ||||
|   }, | ||||
|   "peerDependencies": { | ||||
|     "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.0.0-beta.0 || ^6.3.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@backstage/cli": "^0.29.5", | ||||
|     "@types/react": "^18.3.12" | ||||
|   }, | ||||
|   "files": [ | ||||
|     "dist" | ||||
|   ] | ||||
| } | ||||
|  | @ -0,0 +1,135 @@ | |||
| ## API Report File for "@backstage-community/plugin-manage-module-tech-insights" | ||||
| 
 | ||||
| > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). | ||||
| 
 | ||||
| ```ts | ||||
| import { ApiFactory } from '@backstage/core-plugin-api'; | ||||
| import { ApiRef } from '@backstage/core-plugin-api'; | ||||
| import type { Check } from '@backstage-community/plugin-tech-insights-common/client'; | ||||
| import { GaugeCardProps } from '@backstage-community/plugin-manage-react'; | ||||
| import { GridOwnProps } from '@mui/material/Grid'; | ||||
| import type { ManageColumnModule } from '@backstage-community/plugin-manage-react'; | ||||
| import type { ManageModuleApi } from '@backstage-community/plugin-manage-react'; | ||||
| import type { ProgressColor } from '@backstage-community/plugin-manage-react'; | ||||
| import { PropsWithChildren } from 'react'; | ||||
| import { default as React_2 } from 'react'; | ||||
| import type { ReactNode } from 'react'; | ||||
| 
 | ||||
| // @public | ||||
| export function createManageTechInsightsApiFactory( | ||||
|   options?: DefaultManageApiOptions, | ||||
| ): ApiFactory< | ||||
|   ManageTechInsights, | ||||
|   DefaultManageTechInsightsApi, | ||||
|   { | ||||
|     [x: string]: unknown; | ||||
|   } | ||||
| >; | ||||
| 
 | ||||
| // @public | ||||
| export interface DefaultManageApiOptions { | ||||
|   checkFilter?: (check: Check) => boolean; | ||||
|   getPercentColor?: (percent: number) => ProgressColor; | ||||
|   mapTitle?: ManageTechInsightsMapTitle; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export class DefaultManageTechInsightsApi implements ManageTechInsights { | ||||
|   constructor(options?: DefaultManageApiOptions); | ||||
|   // (undocumented) | ||||
|   readonly checkFilter: (check: Check) => boolean; | ||||
|   // (undocumented) | ||||
|   readonly getPercentColor: (percent: number) => ProgressColor; | ||||
|   // (undocumented) | ||||
|   getProvider: () => typeof ManageProviderTechInsights; | ||||
|   // (undocumented) | ||||
|   readonly mapTitle: ManageTechInsightsMapTitle; | ||||
| } | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export function ManageProviderTechInsights( | ||||
|   props: PropsWithChildren<{}>, | ||||
| ): React_2.JSX.Element; | ||||
| 
 | ||||
| // @public | ||||
| export interface ManageTechInsights extends ManageModuleApi { | ||||
|   checkFilter: (check: Check) => boolean; | ||||
|   getPercentColor: (percent: number) => ProgressColor; | ||||
|   mapTitle: ManageTechInsightsMapTitle; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export const manageTechInsightsApiRef: ApiRef<ManageTechInsights>; | ||||
| 
 | ||||
| // @public | ||||
| export function ManageTechInsightsCards( | ||||
|   props: ManageTechInsightsCardsProps, | ||||
| ): React_2.JSX.Element; | ||||
| 
 | ||||
| // @public | ||||
| export interface ManageTechInsightsCardsProps { | ||||
|   // (undocumented) | ||||
|   containerProps?: Pick< | ||||
|     GridOwnProps, | ||||
|     | 'classes' | ||||
|     | 'columns' | ||||
|     | 'columnSpacing' | ||||
|     | 'direction' | ||||
|     | 'rowSpacing' | ||||
|     | 'spacing' | ||||
|     | 'sx' | ||||
|     | 'wrap' | ||||
|     | 'zeroMinWidth' | ||||
|   >; | ||||
|   // (undocumented) | ||||
|   gaugeCardProps?: GaugeCardProps; | ||||
|   inAccordion?: boolean; | ||||
|   mapTitle?: ManageTechInsightsMapTitle; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export function manageTechInsightsColumns( | ||||
|   options?: ManageTechInsightsOptions, | ||||
| ): ManageColumnModule; | ||||
| 
 | ||||
| // @public | ||||
| export function ManageTechInsightsGrid( | ||||
|   props: ManageTechInsightsGridProps, | ||||
| ): React_2.JSX.Element; | ||||
| 
 | ||||
| // @public | ||||
| export interface ManageTechInsightsGridProps { | ||||
|   inAccordion?: boolean; | ||||
|   mapTitle?: ManageTechInsightsMapTitle; | ||||
| } | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export type ManageTechInsightsMapTitle = ( | ||||
|   check: Check, | ||||
| ) => ManageTechInsightsTitle; | ||||
| 
 | ||||
| // @public | ||||
| export interface ManageTechInsightsOptions { | ||||
|   checkFilter?: (check: Check) => boolean; | ||||
|   combined?: boolean; | ||||
|   showEmpty?: boolean; | ||||
| } | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export type ManageTechInsightsTitle = | ||||
|   | ManageTechInsightsTitleAsObject | ||||
|   | ManageTechInsightsTitleAsElement; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export type ManageTechInsightsTitleAsElement = { | ||||
|   content: ReactNode; | ||||
| }; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export type ManageTechInsightsTitleAsObject = { | ||||
|   title: string; | ||||
|   tooltip?: ReactNode; | ||||
| }; | ||||
| 
 | ||||
| // (No @packageDocumentation comment for this package) | ||||
| ``` | ||||
|  | @ -0,0 +1,77 @@ | |||
| /* | ||||
|  * Copyright 2025 The Backstage Authors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| import type { Check } from '@backstage-community/plugin-tech-insights-common/client'; | ||||
| import type { ProgressColor } from '@backstage-community/plugin-manage-react'; | ||||
| 
 | ||||
| import type { ManageTechInsights } from './ManageTechInsights'; | ||||
| import { ManageProviderTechInsights } from '../components/ManageProvider'; | ||||
| import { ManageTechInsightsMapTitle } from '../title'; | ||||
| 
 | ||||
| function defaultGetPercentColor(percent: number): ProgressColor { | ||||
|   if (percent >= 100) return 'success'; | ||||
|   else if (percent > 50) return 'warning'; | ||||
|   return 'error'; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Options for the {@link DefaultManageTechInsightsApi}. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export interface DefaultManageApiOptions { | ||||
|   /** | ||||
|    * Custom filter to only show certain checks. | ||||
|    */ | ||||
|   checkFilter?: (check: Check) => boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * Override the default colors for gauges. | ||||
|    * | ||||
|    * @param percent - number between 0 and 100 | ||||
|    */ | ||||
|   getPercentColor?: (percent: number) => ProgressColor; | ||||
| 
 | ||||
|   /** | ||||
|    * The default mapping of checks to titles for the | ||||
|    * {@link ManageTechInsightsCards} and {@link ManageTechInsightsGrid} | ||||
|    * components. | ||||
|    */ | ||||
|   mapTitle?: ManageTechInsightsMapTitle; | ||||
| } | ||||
| 
 | ||||
| const defaultMapTitle: ManageTechInsightsMapTitle = check => ({ | ||||
|   title: check.name, | ||||
|   tooltip: check.description, | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * Default implementation of the {@link ManageTechInsights} API. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export class DefaultManageTechInsightsApi implements ManageTechInsights { | ||||
|   readonly checkFilter: (check: Check) => boolean; | ||||
|   readonly getPercentColor: (percent: number) => ProgressColor; | ||||
|   readonly mapTitle: ManageTechInsightsMapTitle; | ||||
| 
 | ||||
|   public constructor(options: DefaultManageApiOptions = {}) { | ||||
|     this.checkFilter = options.checkFilter ?? (() => true); | ||||
|     this.getPercentColor = options.getPercentColor ?? defaultGetPercentColor; | ||||
|     this.mapTitle = options.mapTitle ?? defaultMapTitle; | ||||
|   } | ||||
| 
 | ||||
|   getProvider = () => ManageProviderTechInsights; | ||||
| } | ||||
|  | @ -0,0 +1,45 @@ | |||
| /* | ||||
|  * 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 type { Check } from '@backstage-community/plugin-tech-insights-common/client'; | ||||
| import type { | ||||
|   ManageModuleApi, | ||||
|   ProgressColor, | ||||
| } from '@backstage-community/plugin-manage-react'; | ||||
| import { ManageTechInsightsMapTitle } from '../title'; | ||||
| 
 | ||||
| /** | ||||
|  * ManageTechInsights API, which is a `ManageModuleApi` with additional | ||||
|  * features used in the manage-module-tech-insights plugin. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export interface ManageTechInsights extends ManageModuleApi { | ||||
|   /** | ||||
|    * Custom filter to only show certain checks. | ||||
|    */ | ||||
|   checkFilter: (check: Check) => boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * Function to deduce what color to use of percentage gauges. | ||||
|    */ | ||||
|   getPercentColor: (percent: number) => ProgressColor; | ||||
| 
 | ||||
|   /** | ||||
|    * The mapping of checks to titles for the `ManageTechInsightsCards` and | ||||
|    * `ManageTechInsightsGrid` | ||||
|    */ | ||||
|   mapTitle: ManageTechInsightsMapTitle; | ||||
| } | ||||
|  | @ -0,0 +1,27 @@ | |||
| /* | ||||
|  * 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 { createApiRef } from '@backstage/core-plugin-api'; | ||||
| 
 | ||||
| import type { ManageTechInsights } from './ManageTechInsights'; | ||||
| 
 | ||||
| /** | ||||
|  * ApiRef for the manage tech insights plugin. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export const manageTechInsightsApiRef = createApiRef<ManageTechInsights>({ | ||||
|   id: 'manage-tech-insights', | ||||
| }); | ||||
|  | @ -0,0 +1,37 @@ | |||
| /* | ||||
|  * Copyright 2025 The Backstage Authors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| import { createApiFactory } from '@backstage/core-plugin-api'; | ||||
| 
 | ||||
| import type { DefaultManageApiOptions } from './DefaultManageTechInsightsApi'; | ||||
| import { DefaultManageTechInsightsApi } from './DefaultManageTechInsightsApi'; | ||||
| import { manageTechInsightsApiRef } from './api'; | ||||
| 
 | ||||
| /** | ||||
|  * Factory for creating the manage tech insights API. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export function createManageTechInsightsApiFactory( | ||||
|   options?: DefaultManageApiOptions, | ||||
| ) { | ||||
|   return createApiFactory({ | ||||
|     api: manageTechInsightsApiRef, | ||||
|     deps: {}, | ||||
|     factory() { | ||||
|       return new DefaultManageTechInsightsApi(options); | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
|  | @ -0,0 +1,22 @@ | |||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
| export type { ManageTechInsights } from './ManageTechInsights'; | ||||
| 
 | ||||
| export type { DefaultManageApiOptions } from './DefaultManageTechInsightsApi'; | ||||
| export { DefaultManageTechInsightsApi } from './DefaultManageTechInsightsApi'; | ||||
| 
 | ||||
| export { manageTechInsightsApiRef } from './api'; | ||||
| export { createManageTechInsightsApiFactory } from './impl'; | ||||
|  | @ -0,0 +1,35 @@ | |||
| /* | ||||
|  * 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 React from 'react'; | ||||
| 
 | ||||
| import { makeStyles } from '@mui/styles'; | ||||
| import Tooltip from '@mui/material/Tooltip'; | ||||
| 
 | ||||
| const useStyles = makeStyles((theme: any) => ({ | ||||
|   root: { | ||||
|     paddingLeft: theme.spacing(2), | ||||
|   }, | ||||
| })); | ||||
| 
 | ||||
| export function NoData() { | ||||
|   const { root } = useStyles(); | ||||
| 
 | ||||
|   return ( | ||||
|     <Tooltip title="No data"> | ||||
|       <div className={root}>−</div> | ||||
|     </Tooltip> | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,79 @@ | |||
| /* | ||||
|  * 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 React, { useMemo } from 'react'; | ||||
| 
 | ||||
| import { stringifyEntityRef } from '@backstage/catalog-model'; | ||||
| import { TechInsightsCheckIcon } from '@backstage-community/plugin-tech-insights'; | ||||
| import type { Check } from '@backstage-community/plugin-tech-insights-common/client'; | ||||
| import { | ||||
|   ColumnIconError, | ||||
|   type ManageColumn, | ||||
|   type GetColumnsFunc, | ||||
| } from '@backstage-community/plugin-manage-react'; | ||||
| 
 | ||||
| import { eqCheck } from '../utils'; | ||||
| 
 | ||||
| import { useEntityInsights } from './hooks'; | ||||
| import { NoData } from './NoData'; | ||||
| 
 | ||||
| export function makeGetColumns( | ||||
|   checkFilter: ((check: Check) => boolean) | undefined, | ||||
|   showEmpty: boolean, | ||||
| ): GetColumnsFunc { | ||||
|   return function useColumns(entities): ManageColumn[] { | ||||
|     const { responses, checks } = useEntityInsights( | ||||
|       entities, | ||||
|       checkFilter, | ||||
|       showEmpty, | ||||
|     ); | ||||
| 
 | ||||
|     return useMemo( | ||||
|       () => | ||||
|         checks.map( | ||||
|           (check): ManageColumn => ({ | ||||
|             id: `tech-insights-${check.id}`, | ||||
|             title: check.name, | ||||
|             render: ({ entity }) => { | ||||
|               const entityRef = stringifyEntityRef(entity); | ||||
|               const response = responses.get(entityRef); | ||||
|               if (!response) { | ||||
|                 return <></>; | ||||
|               } | ||||
| 
 | ||||
|               const foundCheck = response.find(res => | ||||
|                 eqCheck(res.check, check), | ||||
|               ); | ||||
| 
 | ||||
|               if (!foundCheck) { | ||||
|                 return <NoData />; | ||||
|               } | ||||
| 
 | ||||
|               return ( | ||||
|                 <TechInsightsCheckIcon | ||||
|                   result={foundCheck} | ||||
|                   entity={entity} | ||||
|                   missingRendererComponent={ | ||||
|                     <ColumnIconError title="No renderer found for this check" /> | ||||
|                   } | ||||
|                 /> | ||||
|               ); | ||||
|             }, | ||||
|           }), | ||||
|         ), | ||||
|       [checks, responses], | ||||
|     ); | ||||
|   }; | ||||
| } | ||||
|  | @ -0,0 +1,182 @@ | |||
| /* | ||||
|  * 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 React, { useMemo } from 'react'; | ||||
| 
 | ||||
| import Tooltip from '@mui/material/Tooltip'; | ||||
| import List from '@mui/material/List'; | ||||
| import ListItem from '@mui/material/ListItem'; | ||||
| import ListItemIcon from '@mui/material/ListItemIcon'; | ||||
| import ListItemText from '@mui/material/ListItemText'; | ||||
| import Grid from '@mui/material/Grid'; | ||||
| import Typography from '@mui/material/Typography'; | ||||
| 
 | ||||
| import { useApi } from '@backstage/core-plugin-api'; | ||||
| import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; | ||||
| import { TechInsightsCheckIcon } from '@backstage-community/plugin-tech-insights'; | ||||
| import type { Check } from '@backstage-community/plugin-tech-insights-common/client'; | ||||
| import { | ||||
|   ColumnIconError, | ||||
|   ColumnIconPercent, | ||||
|   type ManageColumn, | ||||
|   type GetColumnFunc, | ||||
|   ProgressColor, | ||||
| } from '@backstage-community/plugin-manage-react'; | ||||
| 
 | ||||
| import { eqCheck } from '../utils'; | ||||
| import { manageTechInsightsApiRef } from '../api'; | ||||
| 
 | ||||
| import { useEntityInsights, UseEntityInsightsResult } from './hooks'; | ||||
| import { NoData } from './NoData'; | ||||
| 
 | ||||
| interface CombinedColumnProps { | ||||
|   entity: Entity; | ||||
|   useEntityInsightsResult: UseEntityInsightsResult; | ||||
|   getPercentColor: (percent: number) => ProgressColor; | ||||
| } | ||||
| 
 | ||||
| function CombinedColumn(props: CombinedColumnProps) { | ||||
|   const { | ||||
|     entity, | ||||
|     useEntityInsightsResult: { responses, checks, renderers }, | ||||
|     getPercentColor, | ||||
|   } = props; | ||||
| 
 | ||||
|   const entityRef = stringifyEntityRef(entity); | ||||
|   const results = responses.get(entityRef); | ||||
|   if (!results) { | ||||
|     return <NoData />; | ||||
|   } | ||||
| 
 | ||||
|   // Order the results the same way all the time
 | ||||
|   const validResults = checks | ||||
|     .flatMap(check => | ||||
|       results | ||||
|         .filter(res => eqCheck(res.check, check)) | ||||
|         .map(result => ({ | ||||
|           result, | ||||
|           renderer: renderers.get(result.check.type), | ||||
|         })), | ||||
|     ) | ||||
|     .filter((v): v is NonNullable<typeof v> => !!v); | ||||
| 
 | ||||
|   const tooltipList = validResults | ||||
|     .map(({ result }) => { | ||||
|       return ( | ||||
|         <ListItem disablePadding key={Math.random()}> | ||||
|           <ListItemIcon> | ||||
|             <TechInsightsCheckIcon | ||||
|               result={result} | ||||
|               entity={entity} | ||||
|               disableLinksMenu | ||||
|               missingRendererComponent={ | ||||
|                 <ColumnIconError title="No renderer found for this check" /> | ||||
|               } | ||||
|             /> | ||||
|           </ListItemIcon> | ||||
|           <ListItemText primary={result.check.name} /> | ||||
|         </ListItem> | ||||
|       ); | ||||
|     }) | ||||
|     .filter((v): v is NonNullable<typeof v> => !!v); | ||||
| 
 | ||||
|   const wrapTooltip = (child: JSX.Element) => | ||||
|     tooltipList.length === 0 ? ( | ||||
|       child | ||||
|     ) : ( | ||||
|       <Tooltip | ||||
|         title={ | ||||
|           <List | ||||
|             disablePadding | ||||
|             component="nav" | ||||
|             aria-label="main mailbox folders" | ||||
|           > | ||||
|             {tooltipList} | ||||
|           </List> | ||||
|         } | ||||
|         children={child} | ||||
|       /> | ||||
|     ); | ||||
| 
 | ||||
|   if (!validResults.length) { | ||||
|     return <NoData />; | ||||
|   } | ||||
| 
 | ||||
|   const succeeded = validResults.filter( | ||||
|     ({ result, renderer }) => !renderer?.isFailed?.(result), | ||||
|   ).length; | ||||
|   const rate = succeeded / validResults.length; | ||||
| 
 | ||||
|   const percent = Math.round(rate * 100); | ||||
| 
 | ||||
|   const color = getPercentColor(percent); | ||||
| 
 | ||||
|   return ( | ||||
|     <Grid container spacing={0}> | ||||
|       {wrapTooltip( | ||||
|         <Grid item> | ||||
|           <div style={{ cursor: 'default' }}> | ||||
|             <ColumnIconPercent percent={percent} color={color} /> | ||||
|           </div> | ||||
|         </Grid>, | ||||
|       )} | ||||
|       <Grid px={1} item alignContent="center"> | ||||
|         <Typography variant="caption"> | ||||
|           {succeeded}/{validResults.length} | ||||
|         </Typography> | ||||
|       </Grid> | ||||
|     </Grid> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export function makeGetColumn( | ||||
|   checkFilter: ((check: Check) => boolean) | undefined, | ||||
|   showEmpty: boolean, | ||||
| ): GetColumnFunc { | ||||
|   return function useColumn(entities): ManageColumn { | ||||
|     const { getPercentColor } = useApi(manageTechInsightsApiRef); | ||||
| 
 | ||||
|     const useEntityInsightsResult = useEntityInsights( | ||||
|       entities, | ||||
|       checkFilter, | ||||
|       showEmpty, | ||||
|     ); | ||||
| 
 | ||||
|     // We need unique id's for the columns if their render function has changed,
 | ||||
|     // or there's gonna be a UI warning from material-table
 | ||||
|     const id = useMemo(() => { | ||||
|       const newId = `${Math.random() * 1.001}`.slice(2); | ||||
|       return newId; | ||||
|       // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||
|     }, [useEntityInsightsResult, getPercentColor]); | ||||
| 
 | ||||
|     return useMemo( | ||||
|       () => ({ | ||||
|         id: `tech-insights-merged-result-${id}`, | ||||
|         title: 'Tech Insights', | ||||
|         render: ({ entity }) => { | ||||
|           return ( | ||||
|             <CombinedColumn | ||||
|               entity={entity} | ||||
|               useEntityInsightsResult={useEntityInsightsResult} | ||||
|               getPercentColor={getPercentColor} | ||||
|             /> | ||||
|           ); | ||||
|         }, | ||||
|       }), | ||||
|       [id, useEntityInsightsResult, getPercentColor], | ||||
|     ); | ||||
|   }; | ||||
| } | ||||
|  | @ -0,0 +1,56 @@ | |||
| /* | ||||
|  * 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 type { Check } from '@backstage-community/plugin-tech-insights-common/client'; | ||||
| import type { ManageColumnModule } from '@backstage-community/plugin-manage-react'; | ||||
| 
 | ||||
| import { makeGetColumn } from './columns-single'; | ||||
| import { makeGetColumns } from './columns-multiple'; | ||||
| 
 | ||||
| /** | ||||
|  * Options for {@link manageTechInsightsColumns}. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export interface ManageTechInsightsOptions { | ||||
|   /** Combine all checks into a single columns with percentage bar */ | ||||
|   combined?: boolean; | ||||
| 
 | ||||
|   /** Only use these checks (defaults to all). */ | ||||
|   checkFilter?: (check: Check) => boolean; | ||||
| 
 | ||||
|   /** Also show checks that are empty for all entities of the certain kind. */ | ||||
|   showEmpty?: boolean; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Create a column module for displaying tech insights checks. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export function manageTechInsightsColumns( | ||||
|   options?: ManageTechInsightsOptions, | ||||
| ): ManageColumnModule { | ||||
|   const { combined = false, checkFilter, showEmpty = false } = options ?? {}; | ||||
| 
 | ||||
|   if (combined) { | ||||
|     return { | ||||
|       getColumn: makeGetColumn(checkFilter, showEmpty), | ||||
|     }; | ||||
|   } | ||||
|   return { | ||||
|     getColumns: makeGetColumns(checkFilter, showEmpty), | ||||
|   }; | ||||
| } | ||||
|  | @ -0,0 +1,52 @@ | |||
| /* | ||||
|  * Copyright 2025 The Backstage Authors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| import { useApi } from '@backstage/core-plugin-api'; | ||||
| import { Entity } from '@backstage/catalog-model'; | ||||
| import { techInsightsApiRef } from '@backstage-community/plugin-tech-insights'; | ||||
| import type { Check } from '@backstage-community/plugin-tech-insights-common/client'; | ||||
| 
 | ||||
| import { useManageTechInsights } from '../components/ManageProvider'; | ||||
| import { filterEmptyChecks, stringifyCheck } from '../utils'; | ||||
| 
 | ||||
| export function useEntityInsights( | ||||
|   entities: Entity[], | ||||
|   checkFilter: ((check: Check) => boolean) | undefined, | ||||
|   showEmpty: boolean, | ||||
| ) { | ||||
|   const { allChecks, bulkCheckResponse, renderers } = | ||||
|     useManageTechInsights(checkFilter); | ||||
|   const techInsightsApi = useApi(techInsightsApiRef); | ||||
| 
 | ||||
|   const { usedChecks, responses } = filterEmptyChecks( | ||||
|     bulkCheckResponse, | ||||
|     entities, | ||||
|     showEmpty, | ||||
|   ); | ||||
| 
 | ||||
|   const filteredChecks = allChecks.filter(check => | ||||
|     showEmpty ? true : usedChecks.has(stringifyCheck(check)), | ||||
|   ); | ||||
| 
 | ||||
|   return { | ||||
|     techInsightsApi, | ||||
|     responses, | ||||
|     checks: filteredChecks, | ||||
|     renderers, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export type UseEntityInsightsResult = ReturnType<typeof useEntityInsights>; | ||||
|  | @ -0,0 +1,17 @@ | |||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
| export type { ManageTechInsightsOptions } from './columns'; | ||||
| export { manageTechInsightsColumns } from './columns'; | ||||
|  | @ -0,0 +1,167 @@ | |||
| /* | ||||
|  * 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 React, { ReactNode, useCallback } from 'react'; | ||||
| 
 | ||||
| import { makeStyles } from '@mui/styles'; | ||||
| import Box from '@mui/material/Box'; | ||||
| import Grid, { GridOwnProps } from '@mui/material/Grid'; | ||||
| import Tooltip from '@mui/material/Tooltip'; | ||||
| 
 | ||||
| import { useApi } from '@backstage/core-plugin-api'; | ||||
| import { GaugePropsGetColor } from '@backstage/core-components'; | ||||
| import { | ||||
|   useCurrentKinds, | ||||
|   useOwnedEntities, | ||||
|   GaugeCard, | ||||
|   GaugeCardProps, | ||||
|   ManageAccordion, | ||||
| } from '@backstage-community/plugin-manage-react'; | ||||
| 
 | ||||
| import { | ||||
|   ResponsesForCheck, | ||||
|   useManageTechInsightsForEntities, | ||||
| } from '../ManageProvider/ManageProviderTechInsights'; | ||||
| import { manageTechInsightsApiRef } from '../../api'; | ||||
| import { | ||||
|   isTitleAsObject, | ||||
|   ManageTechInsightsMapTitle, | ||||
|   ManageTechInsightsTitle, | ||||
| } from '../../title'; | ||||
| import { useAccordionTitle } from '../../utils'; | ||||
| 
 | ||||
| const useStyles = makeStyles({ | ||||
|   root: { | ||||
|     cursor: 'default', | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * Props for {@link ManageTechInsightsCards}. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export interface ManageTechInsightsCardsProps { | ||||
|   containerProps?: Pick< | ||||
|     GridOwnProps, | ||||
|     | 'classes' | ||||
|     | 'columns' | ||||
|     | 'columnSpacing' | ||||
|     | 'direction' | ||||
|     | 'rowSpacing' | ||||
|     | 'spacing' | ||||
|     | 'sx' | ||||
|     | 'wrap' | ||||
|     | 'zeroMinWidth' | ||||
|   >; | ||||
|   gaugeCardProps?: GaugeCardProps; | ||||
| 
 | ||||
|   /** | ||||
|    * Map the title of the check to either an object `{ title, tooltip? }` or to | ||||
|    * a JSX element. | ||||
|    */ | ||||
|   mapTitle?: ManageTechInsightsMapTitle; | ||||
| 
 | ||||
|   /** | ||||
|    * Render the cards inside an accordion. | ||||
|    * | ||||
|    * Defaults to false. | ||||
|    */ | ||||
|   inAccordion?: boolean; | ||||
| } | ||||
| 
 | ||||
| function Title({ | ||||
|   titleInfo, | ||||
| }: { | ||||
|   titleInfo: ManageTechInsightsTitle; | ||||
| }): ReactNode { | ||||
|   const { root } = useStyles(); | ||||
| 
 | ||||
|   if (isTitleAsObject(titleInfo)) { | ||||
|     return titleInfo.tooltip ? ( | ||||
|       <Tooltip title={titleInfo.tooltip}> | ||||
|         <div className={root}>{titleInfo.title}</div> | ||||
|       </Tooltip> | ||||
|     ) : ( | ||||
|       <div className={root}>{titleInfo.title}</div> | ||||
|     ); | ||||
|   } | ||||
|   return <div className={root}>{titleInfo.content}</div>; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Display a set of cards for the tech insights checks given the current shown | ||||
|  * entities. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export function ManageTechInsightsCards(props: ManageTechInsightsCardsProps) { | ||||
|   const { containerProps, gaugeCardProps, inAccordion } = props; | ||||
| 
 | ||||
|   const kinds = useCurrentKinds(); | ||||
|   const entities = useOwnedEntities(kinds); | ||||
| 
 | ||||
|   const { checks, responsesForCheck } = | ||||
|     useManageTechInsightsForEntities(entities); | ||||
| 
 | ||||
|   const { getPercentColor, mapTitle: defaultMapTitle } = useApi( | ||||
|     manageTechInsightsApiRef, | ||||
|   ); | ||||
| 
 | ||||
|   const mapTitle = props.mapTitle ?? defaultMapTitle; | ||||
| 
 | ||||
|   const getRatio = (theseResponses: ResponsesForCheck) => { | ||||
|     const tot = theseResponses.length; | ||||
|     const succ = tot - theseResponses.filter(resp => resp.failed).length; | ||||
| 
 | ||||
|     return tot === 0 ? 1 : succ / tot; | ||||
|   }; | ||||
| 
 | ||||
|   const getColor = useCallback<GaugePropsGetColor>( | ||||
|     args => { | ||||
|       const rawColor = getPercentColor(args.value); | ||||
|       const muiColor = | ||||
|         rawColor === 'inherit' ? 'inherit' : args.palette[rawColor].main; | ||||
| 
 | ||||
|       return muiColor; | ||||
|     }, | ||||
|     [getPercentColor], | ||||
|   ); | ||||
| 
 | ||||
|   const grid = ( | ||||
|     <Grid columnSpacing={2} marginBottom={2} {...containerProps} container> | ||||
|       {checks.map(({ check, uniq }) => ( | ||||
|         <Grid item key={uniq} padding={0}> | ||||
|           <GaugeCard | ||||
|             progress={getRatio(responsesForCheck.get(uniq) ?? [])} | ||||
|             gaugeCardProps={gaugeCardProps} | ||||
|             title={<Title titleInfo={mapTitle(check)} />} | ||||
|             getColor={getColor} | ||||
|           /> | ||||
|         </Grid> | ||||
|       ))} | ||||
|     </Grid> | ||||
|   ); | ||||
| 
 | ||||
|   const accordionTitle = useAccordionTitle(); | ||||
| 
 | ||||
|   return inAccordion ? ( | ||||
|     <ManageAccordion title={accordionTitle} name="tech-insights"> | ||||
|       {grid} | ||||
|     </ManageAccordion> | ||||
|   ) : ( | ||||
|     <Box>{grid}</Box> | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,17 @@ | |||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
| export type { ManageTechInsightsCardsProps } from './Cards'; | ||||
| export { ManageTechInsightsCards } from './Cards'; | ||||
|  | @ -0,0 +1,146 @@ | |||
| /* | ||||
|  * 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 React, { ReactNode, useCallback, useMemo } from 'react'; | ||||
| 
 | ||||
| import { makeStyles, useTheme } from '@mui/styles'; | ||||
| import Box from '@mui/material/Box'; | ||||
| import Tooltip from '@mui/material/Tooltip'; | ||||
| 
 | ||||
| import { useApi } from '@backstage/core-plugin-api'; | ||||
| import { | ||||
|   useCurrentKinds, | ||||
|   useOwnedEntities, | ||||
|   GaugeGrid, | ||||
|   ManageAccordion, | ||||
| } from '@backstage-community/plugin-manage-react'; | ||||
| 
 | ||||
| import { | ||||
|   ResponsesForCheck, | ||||
|   useManageTechInsightsForEntities, | ||||
| } from '../ManageProvider/ManageProviderTechInsights'; | ||||
| import { manageTechInsightsApiRef } from '../../api'; | ||||
| import { | ||||
|   isTitleAsObject, | ||||
|   ManageTechInsightsMapTitle, | ||||
|   ManageTechInsightsTitle, | ||||
| } from '../../title'; | ||||
| import { useAccordionTitle } from '../../utils'; | ||||
| 
 | ||||
| const useStyles = makeStyles({ | ||||
|   root: { | ||||
|     cursor: 'default', | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  * Props for {@link ManageTechInsightsGrid}. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export interface ManageTechInsightsGridProps { | ||||
|   /** | ||||
|    * Map the title of the check to either an object `{ title, tooltip? }` or to | ||||
|    * a JSX element. | ||||
|    */ | ||||
|   mapTitle?: ManageTechInsightsMapTitle; | ||||
| 
 | ||||
|   /** | ||||
|    * Render the grid inside an accordion. | ||||
|    * | ||||
|    * Defaults to true. | ||||
|    */ | ||||
|   inAccordion?: boolean; | ||||
| } | ||||
| 
 | ||||
| function Title({ | ||||
|   titleInfo, | ||||
| }: { | ||||
|   titleInfo: ManageTechInsightsTitle; | ||||
| }): ReactNode { | ||||
|   const { root } = useStyles(); | ||||
| 
 | ||||
|   if (isTitleAsObject(titleInfo)) { | ||||
|     return titleInfo.tooltip ? ( | ||||
|       <Tooltip title={titleInfo.tooltip}> | ||||
|         <div className={root}>{titleInfo.title}</div> | ||||
|       </Tooltip> | ||||
|     ) : ( | ||||
|       <div className={root}>{titleInfo.title}</div> | ||||
|     ); | ||||
|   } | ||||
|   return <div className={root}>{titleInfo.content}</div>; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Display a set of grid boxes for the tech insights checks given the current | ||||
|  * shown entities. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export function ManageTechInsightsGrid(props: ManageTechInsightsGridProps) { | ||||
|   const { palette } = useTheme(); | ||||
|   const kinds = useCurrentKinds(); | ||||
|   const entities = useOwnedEntities(kinds); | ||||
| 
 | ||||
|   const { inAccordion = true } = props; | ||||
| 
 | ||||
|   const { checks, responsesForCheck } = | ||||
|     useManageTechInsightsForEntities(entities); | ||||
| 
 | ||||
|   const { getPercentColor, mapTitle: defaultMapTitle } = useApi( | ||||
|     manageTechInsightsApiRef, | ||||
|   ); | ||||
| 
 | ||||
|   const mapTitle = props.mapTitle ?? defaultMapTitle; | ||||
| 
 | ||||
|   const getRatio = (theseResponses: ResponsesForCheck) => { | ||||
|     const tot = theseResponses.length; | ||||
|     const succ = tot - theseResponses.filter(resp => resp.failed).length; | ||||
| 
 | ||||
|     return tot === 0 ? 1 : succ / tot; | ||||
|   }; | ||||
| 
 | ||||
|   const getColor = useCallback( | ||||
|     (progress: number) => { | ||||
|       const rawColor = getPercentColor(progress * 100); | ||||
|       const muiColor = | ||||
|         rawColor === 'inherit' | ||||
|           ? 'inherit' | ||||
|           : palette[rawColor]?.main ?? 'inherit'; | ||||
|       return muiColor; | ||||
|     }, | ||||
|     [getPercentColor, palette], | ||||
|   ); | ||||
| 
 | ||||
|   const items = useMemo(() => { | ||||
|     return checks.map(({ check, uniq }) => ({ | ||||
|       title: <Title titleInfo={mapTitle(check)} />, | ||||
|       progress: getRatio(responsesForCheck.get(uniq) ?? []), | ||||
|     })); | ||||
|   }, [checks, mapTitle, responsesForCheck]); | ||||
| 
 | ||||
|   const accordionTitle = useAccordionTitle(); | ||||
| 
 | ||||
|   return inAccordion ? ( | ||||
|     <ManageAccordion title={accordionTitle} name="tech-insights"> | ||||
|       <GaugeGrid items={items} getColor={getColor} /> | ||||
|     </ManageAccordion> | ||||
|   ) : ( | ||||
|     <Box> | ||||
|       <GaugeGrid items={items} getColor={getColor} /> | ||||
|     </Box> | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,17 @@ | |||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
| export type { ManageTechInsightsGridProps } from './Grid'; | ||||
| export { ManageTechInsightsGrid } from './Grid'; | ||||
|  | @ -0,0 +1,202 @@ | |||
| /* | ||||
|  * 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 React, { | ||||
|   PropsWithChildren, | ||||
|   createContext, | ||||
|   useContext, | ||||
|   useMemo, | ||||
| } from 'react'; | ||||
| 
 | ||||
| import useAsync from 'react-use/lib/useAsync'; | ||||
| 
 | ||||
| import { useApi } from '@backstage/core-plugin-api'; | ||||
| import { | ||||
|   Entity, | ||||
|   parseEntityRef, | ||||
|   stringifyEntityRef, | ||||
| } from '@backstage/catalog-model'; | ||||
| import { | ||||
|   CheckResultRenderer, | ||||
|   techInsightsApiRef, | ||||
| } from '@backstage-community/plugin-tech-insights'; | ||||
| import { Check } from '@backstage-community/plugin-tech-insights-common/client'; | ||||
| import { | ||||
|   BulkCheckResponse, | ||||
|   CheckResult, | ||||
| } from '@backstage-community/plugin-tech-insights-common'; | ||||
| import { useManagedEntities } from '@backstage-community/plugin-manage-react'; | ||||
| 
 | ||||
| import { stringifyCheck } from '../../utils'; | ||||
| import { manageTechInsightsApiRef } from '../../api/api'; | ||||
| 
 | ||||
| /** | ||||
|  * @internal | ||||
|  */ | ||||
| export interface ManageTechInsightsContext { | ||||
|   allChecks: Check[]; | ||||
|   bulkCheckResponse: BulkCheckResponse | undefined; | ||||
|   renderers: Map<string, CheckResultRenderer>; | ||||
| } | ||||
| 
 | ||||
| const ctx = createContext<ManageTechInsightsContext>(undefined as any); | ||||
| 
 | ||||
| const { Provider } = ctx; | ||||
| 
 | ||||
| /** @public */ | ||||
| export function ManageProviderTechInsights(props: PropsWithChildren<{}>) { | ||||
|   const techInsightsApi = useApi(techInsightsApiRef); | ||||
|   const ownedEntities = useManagedEntities(); | ||||
| 
 | ||||
|   const asyncState = useAsync(async () => { | ||||
|     const entityRefs = ownedEntities.map(entity => | ||||
|       parseEntityRef(stringifyEntityRef(entity)), | ||||
|     ); | ||||
| 
 | ||||
|     const [allChecks, bulkCheckResponse] = await Promise.all([ | ||||
|       techInsightsApi.getAllChecks(), | ||||
|       techInsightsApi.runBulkChecks(entityRefs), | ||||
|     ]); | ||||
| 
 | ||||
|     return { allChecks, bulkCheckResponse }; | ||||
|   }, [ownedEntities, techInsightsApi]); | ||||
| 
 | ||||
|   const state: ManageTechInsightsContext = useMemo(() => { | ||||
|     const allChecks = asyncState.value?.allChecks ?? []; | ||||
|     const bulkCheckResponse = asyncState.value?.bulkCheckResponse; | ||||
| 
 | ||||
|     const allRenderers = techInsightsApi.getCheckResultRenderers( | ||||
|       allChecks.map(check => check.type), | ||||
|     ); | ||||
|     const renderers = new Map( | ||||
|       allRenderers.map(renderer => [renderer.type, renderer]), | ||||
|     ); | ||||
| 
 | ||||
|     return { | ||||
|       allChecks, | ||||
|       bulkCheckResponse, | ||||
|       renderers, | ||||
|     }; | ||||
|   }, [asyncState.value, techInsightsApi]); | ||||
| 
 | ||||
|   return <Provider value={state} children={props.children} />; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * @internal | ||||
|  */ | ||||
| export function useManageTechInsights(checkFilter?: (check: Check) => boolean) { | ||||
|   const manageTechInsightsApi = useApi(manageTechInsightsApiRef); | ||||
|   const context = useContext(ctx); | ||||
| 
 | ||||
|   const filter = checkFilter ?? manageTechInsightsApi.checkFilter; | ||||
| 
 | ||||
|   return useMemo(() => { | ||||
|     return { | ||||
|       ...context, | ||||
|       allChecks: context.allChecks.filter(filter), | ||||
|       bulkCheckResponse: (context.bulkCheckResponse ?? []).map(response => ({ | ||||
|         ...response, | ||||
|         results: response.results.filter(res => filter(res.check)), | ||||
|       })), | ||||
|     }; | ||||
|   }, [context, filter]); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * @internal | ||||
|  */ | ||||
| export type ResponsesForCheck = Array<{ | ||||
|   entity: string; | ||||
|   result: CheckResult; | ||||
|   renderer: CheckResultRenderer | undefined; | ||||
|   failed: boolean; | ||||
| }>; | ||||
| 
 | ||||
| /** | ||||
|  * @internal | ||||
|  */ | ||||
| export interface DecoratedCheck { | ||||
|   uniq: string; | ||||
|   renderer: CheckResultRenderer | undefined; | ||||
|   check: Check; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * @internal | ||||
|  */ | ||||
| export interface UseManageTechInsightsForEntitiesResult { | ||||
|   checks: DecoratedCheck[]; | ||||
|   responses: BulkCheckResponse; | ||||
|   responsesForCheck: Map<string, ResponsesForCheck>; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * @internal | ||||
|  */ | ||||
| export function useManageTechInsightsForEntities( | ||||
|   entities: Entity[], | ||||
|   checkFilter?: (check: Check) => boolean, | ||||
| ): UseManageTechInsightsForEntitiesResult { | ||||
|   const { allChecks, bulkCheckResponse, renderers } = | ||||
|     useManageTechInsights(checkFilter); | ||||
| 
 | ||||
|   return useMemo((): UseManageTechInsightsForEntitiesResult => { | ||||
|     const entitySet = new Set( | ||||
|       entities.map(entity => | ||||
|         stringifyEntityRef(entity).toLocaleLowerCase('en-US'), | ||||
|       ), | ||||
|     ); | ||||
| 
 | ||||
|     const responses = (bulkCheckResponse ?? []).filter(resp => | ||||
|       entitySet.has(resp.entity.toLocaleLowerCase('en-US')), | ||||
|     ); | ||||
| 
 | ||||
|     const responsesForCheck = new Map<string, ResponsesForCheck>(); | ||||
| 
 | ||||
|     const uniqueChecks = new Set( | ||||
|       responses.flatMap(resp => | ||||
|         resp.results.map(chkRes => { | ||||
|           const stringified = stringifyCheck(chkRes.check); | ||||
| 
 | ||||
|           const renderer = renderers.get(chkRes.check.type); | ||||
| 
 | ||||
|           const checkResponses = responsesForCheck.get(stringified) ?? []; | ||||
|           checkResponses.push({ | ||||
|             entity: resp.entity, | ||||
|             result: chkRes, | ||||
|             renderer, | ||||
|             failed: !renderer?.isFailed ? false : renderer.isFailed(chkRes), | ||||
|           }); | ||||
|           responsesForCheck.set(stringified, checkResponses); | ||||
| 
 | ||||
|           return stringified; | ||||
|         }), | ||||
|       ), | ||||
|     ); | ||||
| 
 | ||||
|     const checks = allChecks | ||||
|       .filter(check => uniqueChecks.has(stringifyCheck(check))) | ||||
|       .map( | ||||
|         (check): DecoratedCheck => ({ | ||||
|           check, | ||||
|           uniq: stringifyCheck(check), | ||||
|           renderer: renderers.get(check.type), | ||||
|         }), | ||||
|       ); | ||||
| 
 | ||||
|     return { checks, responses, responsesForCheck }; | ||||
|   }, [allChecks, bulkCheckResponse, renderers, entities]); | ||||
| } | ||||
|  | @ -0,0 +1,20 @@ | |||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
| export type { ManageTechInsightsContext } from './ManageProviderTechInsights'; | ||||
| export { | ||||
|   ManageProviderTechInsights, | ||||
|   useManageTechInsights, | ||||
| } from './ManageProviderTechInsights'; | ||||
|  | @ -0,0 +1,20 @@ | |||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
| export type { ManageTechInsightsCardsProps } from './Cards'; | ||||
| export { ManageTechInsightsCards } from './Cards'; | ||||
| 
 | ||||
| export type { ManageTechInsightsGridProps } from './Grid'; | ||||
| export { ManageTechInsightsGrid } from './Grid'; | ||||
|  | @ -0,0 +1,32 @@ | |||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
| export type * from './api'; | ||||
| export * from './api'; | ||||
| 
 | ||||
| export type * from './components'; | ||||
| export * from './components'; | ||||
| 
 | ||||
| export type { ManageProviderTechInsights } from './components/ManageProvider'; | ||||
| 
 | ||||
| export type { ManageTechInsightsOptions } from './columns'; | ||||
| export { manageTechInsightsColumns } from './columns'; | ||||
| 
 | ||||
| export type { | ||||
|   ManageTechInsightsTitleAsObject, | ||||
|   ManageTechInsightsTitleAsElement, | ||||
|   ManageTechInsightsTitle, | ||||
|   ManageTechInsightsMapTitle, | ||||
| } from './title'; | ||||
|  | @ -0,0 +1,16 @@ | |||
| /* | ||||
|  * Copyright 2024 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. | ||||
|  */ | ||||
| export {}; | ||||
|  | @ -0,0 +1,45 @@ | |||
| /* | ||||
|  * 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 type { ReactNode } from 'react'; | ||||
| 
 | ||||
| import type { Check } from '@backstage-community/plugin-tech-insights-common/client'; | ||||
| 
 | ||||
| /** @public */ | ||||
| export type ManageTechInsightsTitleAsObject = { | ||||
|   title: string; | ||||
|   tooltip?: ReactNode; | ||||
| }; | ||||
| 
 | ||||
| /** @public */ | ||||
| export type ManageTechInsightsTitleAsElement = { content: ReactNode }; | ||||
| 
 | ||||
| /** @public */ | ||||
| export type ManageTechInsightsTitle = | ||||
|   | ManageTechInsightsTitleAsObject | ||||
|   | ManageTechInsightsTitleAsElement; | ||||
| 
 | ||||
| /** @public */ | ||||
| export type ManageTechInsightsMapTitle = ( | ||||
|   check: Check, | ||||
| ) => ManageTechInsightsTitle; | ||||
| 
 | ||||
| export function isTitleAsObject( | ||||
|   titleInfo: ManageTechInsightsTitle, | ||||
| ): titleInfo is ManageTechInsightsTitleAsObject { | ||||
|   return ( | ||||
|     typeof (titleInfo as ManageTechInsightsTitleAsObject).title !== 'undefined' | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,79 @@ | |||
| /* | ||||
|  * 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 { stringifyEntityRef, type Entity } from '@backstage/catalog-model'; | ||||
| 
 | ||||
| import type { Check } from '@backstage-community/plugin-tech-insights-common/client'; | ||||
| import type { | ||||
|   BulkCheckResponse, | ||||
|   CheckResponse, | ||||
|   CheckResult, | ||||
| } from '@backstage-community/plugin-tech-insights-common'; | ||||
| import { useCurrentKindTitle } from '@backstage-community/plugin-manage-react'; | ||||
| 
 | ||||
| export function stringifyCheck(check: Check | CheckResponse): string { | ||||
|   return `${check.id}-${check.name}-${check.type}`; | ||||
| } | ||||
| 
 | ||||
| export function eqCheck(a: Check | CheckResponse, b: Check | CheckResponse) { | ||||
|   return stringifyCheck(a) === stringifyCheck(b); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Given a bulk response, it filters checks that are defined for the given | ||||
|  * entities. | ||||
|  * | ||||
|  * It also filters out checks that are undefined for all entities, if chosen to. | ||||
|  * | ||||
|  * Returns: | ||||
|  *   { | ||||
|  *     responses: Map<entity string ref, CheckResult[]> | ||||
|  *     usedChecks: Set<string> // stringified checks
 | ||||
|  *   } | ||||
|  * | ||||
|  * @internal | ||||
|  */ | ||||
| export function filterEmptyChecks( | ||||
|   bulkCheckResponse: BulkCheckResponse | undefined, | ||||
|   entities: Entity[], | ||||
|   includeEmpty = false, | ||||
| ): { responses: Map<string, CheckResult[]>; usedChecks: Set<string> } { | ||||
|   const responses = new Map<string, CheckResult[]>(); | ||||
| 
 | ||||
|   const usedChecks = new Set<string>(); | ||||
| 
 | ||||
|   const entitiesSet = new Set( | ||||
|     entities.map(entity => stringifyEntityRef(entity)), | ||||
|   ); | ||||
| 
 | ||||
|   bulkCheckResponse | ||||
|     ?.filter(resp => entitiesSet.has(resp.entity)) | ||||
|     ?.forEach(resp => { | ||||
|       responses.set(resp.entity, resp.results); | ||||
| 
 | ||||
|       resp.results.forEach(res => { | ||||
|         if (includeEmpty || typeof res.result !== 'undefined') | ||||
|           usedChecks.add(stringifyCheck(res.check)); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|   return { responses, usedChecks }; | ||||
| } | ||||
| 
 | ||||
| export function useAccordionTitle() { | ||||
|   const kindTitle = useCurrentKindTitle(); | ||||
| 
 | ||||
|   return `Tech insights of your ${kindTitle}`; | ||||
| } | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); | ||||
|  | @ -0,0 +1,86 @@ | |||
| # Manage, react package | ||||
| 
 | ||||
| This package is used by the Manage plugin, and contains components and hooks useful for building components to be used in the Manage page, in an entity tab or outside, or an entity table column. | ||||
| 
 | ||||
| It also acts as the base for all packages that extends the entity table columns and need a provider. Those packages can provide an api ref of a type that extends the exported type `ManageModuleApi`. When defining the `manageApiRef`, an implementor can add this api ref when doing `createManageApiFactory([...])`. | ||||
| 
 | ||||
| The hooks in this package can generally be used anywhere in the manage page unless stated otherwise. | ||||
| 
 | ||||
| ## Hooks | ||||
| 
 | ||||
| ### useOwnedKinds | ||||
| 
 | ||||
| Returns the kinds configured in the ManagePage component (or defaults to the original kinds in Backstage). If the parameter `onlyOwned` is true, it will only return the kinds of entities actually owned by the user. | ||||
| 
 | ||||
| ### useOwnedEntities | ||||
| 
 | ||||
| Returns an array of all owned entities. By passing a kind (or array of kinds) as parameter, only entities of those kinds are returned. | ||||
| 
 | ||||
| ### useManagedEntities | ||||
| 
 | ||||
| Returns all managed entities, i.e. owned entities and starred entities. | ||||
| 
 | ||||
| ### useOwners | ||||
| 
 | ||||
| Returns an object on the form: | ||||
| 
 | ||||
| ```ts | ||||
| { | ||||
|   groups: Entity[]; | ||||
|   ownedEntityRefs: string[]; | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| `ownedEntityRefs` is a list of entity refs for all owners (incl. the current user). `groups` is a list of `Entity` objects for the owners that are groups. | ||||
| 
 | ||||
| These lists are ordered by: | ||||
| 
 | ||||
| 1. Immediate group membership | ||||
| 2. Groups higher up the group hierarchy | ||||
| 3. User | ||||
| 
 | ||||
| Within each such category, the entities are ordered by their title/name alpha-numerically. | ||||
| 
 | ||||
| ### useCurrentKind | ||||
| 
 | ||||
| Returns the _current_ kind, if used within an entity tab. If called from outside an entity tab, returns `undefined`. | ||||
| 
 | ||||
| ### useCurrentKinds | ||||
| 
 | ||||
| Same as `useCurrentKind` but returns an array of kinds, either only the _current_ kind, but fallbacks to the result of `useOwnedKinds`, | ||||
| 
 | ||||
| ### useCurrentKindTitle | ||||
| 
 | ||||
| Returns the name (title) of the current kind, e.g. "components" or "systems". Can also be "entities" if the combined view is used, or "starred entities" if that tab is active. | ||||
| 
 | ||||
| This can be used by modules that extend the page, and is currently used by `@backstage-community/plugin-manage-module-tech-insights` for the accordion title. | ||||
| 
 | ||||
| ## Components | ||||
| 
 | ||||
| ### Accordion | ||||
| 
 | ||||
| This is a MUI Accordion with the expanded state saved in user settings. | ||||
| 
 | ||||
| ### GaugeCard | ||||
| 
 | ||||
| This is the `@backstage/core-components` `GuageCard` component with pre-defined props to make them appear the same, when showing multiple gauges from different plugin modules. | ||||
| 
 | ||||
| ### GaugeGrid | ||||
| 
 | ||||
| Similar to `GaugeCard`, `GaugeGrid` can be used instead, which shows smaller cards for each Gauge. | ||||
| 
 | ||||
| ### ColumnIconError | ||||
| 
 | ||||
| When implementing a column provider, this component can act as a fallback error icon. | ||||
| 
 | ||||
| ### ColumnIconPercent | ||||
| 
 | ||||
| When implementing a column provider, this component can show a (circular) percentage gauge. | ||||
| 
 | ||||
| ### ReorderableTabs | ||||
| 
 | ||||
| A component rendering tabs (although as a button group) with drag-and-drop support. This is used in the Settings page to give the user the ability to reorder the tabs and kinds. | ||||
| 
 | ||||
| ### TabContentFullHeight | ||||
| 
 | ||||
| The helper component `TabContentFullHeight` can be used as a wrapper around the content for a tab. It sets its exact height to adapt to the screen size (and updates when the window changes size). The optional boolean prop `resizeChild` which can be set to also update the size of the (one and only) child component. The prop `bottomMargin` can be used to set a bottom margin other than the default. | ||||
|  | @ -0,0 +1,10 @@ | |||
| apiVersion: backstage.io/v1alpha1 | ||||
| kind: Component | ||||
| metadata: | ||||
|   name: backstage-plugin-manage-react | ||||
|   title: '@backstage/plugin-manage-react' | ||||
|   description: Manage page. React components. | ||||
| spec: | ||||
|   lifecycle: experimental | ||||
|   type: backstage-frontend-plugin | ||||
|   owner: maintainers | ||||
|  | @ -0,0 +1,73 @@ | |||
| { | ||||
|   "name": "@backstage-community/plugin-manage-react", | ||||
|   "description": "Manage plugin - react package", | ||||
|   "version": "0.1.0", | ||||
|   "backstage": { | ||||
|     "role": "web-library", | ||||
|     "pluginId": "manage", | ||||
|     "pluginPackages": [ | ||||
|       "@backstage-community/plugin-manage", | ||||
|       "@backstage-community/plugin-manage-react" | ||||
|     ] | ||||
|   }, | ||||
|   "main": "src/index.ts", | ||||
|   "types": "src/index.ts", | ||||
|   "license": "Apache-2.0", | ||||
|   "publishConfig": { | ||||
|     "access": "public", | ||||
|     "main": "dist/index.esm.js", | ||||
|     "types": "dist/index.d.ts" | ||||
|   }, | ||||
|   "homepage": "https://backstage.io", | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "https://github.com/backstage/backstage", | ||||
|     "directory": "plugins/manage-react" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "backstage", | ||||
|     "manage", | ||||
|     "page", | ||||
|     "manage-page", | ||||
|     "components" | ||||
|   ], | ||||
|   "sideEffects": false, | ||||
|   "scripts": { | ||||
|     "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.3", | ||||
|     "@backstage/core-components": "^0.16.3", | ||||
|     "@backstage/core-plugin-api": "^1.10.3", | ||||
|     "@backstage/plugin-catalog-react": "^1.15.1", | ||||
|     "@backstage/types": "^1.2.1", | ||||
|     "@dnd-kit/core": "^6.3.1", | ||||
|     "@dnd-kit/modifiers": "^9.0.0", | ||||
|     "@dnd-kit/sortable": "^10.0.0", | ||||
|     "@mui/icons-material": "^5.16.7", | ||||
|     "@mui/material": "^5.15.16", | ||||
|     "@mui/styles": "^5", | ||||
|     "already": "^2.2.1", | ||||
|     "pluralize": "^8.0.0", | ||||
|     "react-use": "^17.5.0" | ||||
|   }, | ||||
|   "peerDependencies": { | ||||
|     "@dnd-kit/utilities": "*", | ||||
|     "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.0.0-beta.0 || ^6.3.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@backstage/cli": "^0.29.5", | ||||
|     "@types/pluralize": "^0.0.33", | ||||
|     "@types/react": "^18.3.12" | ||||
|   }, | ||||
|   "files": [ | ||||
|     "dist" | ||||
|   ] | ||||
| } | ||||
|  | @ -0,0 +1,417 @@ | |||
| ## API Report File for "@backstage-community/plugin-manage-react" | ||||
| 
 | ||||
| > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). | ||||
| 
 | ||||
| ```ts | ||||
| /// <reference types="react" /> | ||||
| 
 | ||||
| import { ApiFactory } from '@backstage/core-plugin-api'; | ||||
| import { ApiRef } from '@backstage/core-plugin-api'; | ||||
| import { BackstagePlugin } from '@backstage/core-plugin-api'; | ||||
| import { CircularProgressProps } from '@mui/material/CircularProgress'; | ||||
| import { ComponentProps } from 'react'; | ||||
| import type { ComponentType } from 'react'; | ||||
| import { Entity } from '@backstage/catalog-model'; | ||||
| import { GaugeCard as GaugeCard_2 } from '@backstage/core-components'; | ||||
| import { GaugePropsGetColor } from '@backstage/core-components'; | ||||
| import { GridOwnProps } from '@mui/material/Grid'; | ||||
| import { JsonValue } from '@backstage/types'; | ||||
| import { PropsWithChildren } from 'react'; | ||||
| import { default as React_2 } from 'react'; | ||||
| import { ReactNode } from 'react'; | ||||
| 
 | ||||
| // @public | ||||
| export interface ApiFactoryOptions { | ||||
|   extensions?: Iterable<ManageModuleApiRef>; | ||||
|   kindOrder?: string[]; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export function arrayify<T>(t: T | T[] | Iterable<T> | undefined): T[]; | ||||
| 
 | ||||
| // @public | ||||
| export function ColumnIconError( | ||||
|   props: ColumnIconErrorProps, | ||||
| ): React_2.JSX.Element; | ||||
| 
 | ||||
| // @public | ||||
| export interface ColumnIconErrorProps { | ||||
|   title?: string; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export function ColumnIconPercent( | ||||
|   props: ColumnIconPercentProps, | ||||
| ): React_2.JSX.Element; | ||||
| 
 | ||||
| // @public | ||||
| export interface ColumnIconPercentProps { | ||||
|   // (undocumented) | ||||
|   color?: ProgressColor; | ||||
|   // (undocumented) | ||||
|   percent: number; | ||||
|   // (undocumented) | ||||
|   title?: string; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export function createManageApiFactory(options?: ApiFactoryOptions): ApiFactory< | ||||
|   ManageApi, | ||||
|   DefaultManageApi, | ||||
|   { | ||||
|     [x: string]: any; | ||||
|   } | ||||
| >; | ||||
| 
 | ||||
| // @public | ||||
| export function createUserSettingsContext<T extends JsonValue>( | ||||
|   feature: string, | ||||
|   settingsKey: string, | ||||
|   options?: CreateUserSettingsContextOptions<T>, | ||||
| ): UserSettingsContextResult<T>; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export interface CreateUserSettingsContextOptions<T extends JsonValue> { | ||||
|   coerce?: (value: JsonValue) => T; | ||||
|   // (undocumented) | ||||
|   defaultValue?: T | undefined; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export function CurrentKindProvider( | ||||
|   props: PropsWithChildren< | ||||
|     | { | ||||
|         kind: string; | ||||
|         starred?: never; | ||||
|       } | ||||
|     | { | ||||
|         kind?: never; | ||||
|         starred: true; | ||||
|       } | ||||
|   >, | ||||
| ): React_2.JSX.Element; | ||||
| 
 | ||||
| // @public | ||||
| export class DefaultManageApi implements ManageApi { | ||||
|   constructor({ kindOrder, providers }: DefaultManageApiOptions); | ||||
|   // (undocumented) | ||||
|   getProviders: () => readonly ManageProvider[]; | ||||
|   // (undocumented) | ||||
|   readonly kindOrder: string[]; | ||||
| } | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export interface DefaultManageApiOptions { | ||||
|   kindOrder?: string[]; | ||||
|   providers: Iterable<ManageModuleApi>; | ||||
| } | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export const GaugeCard: ManageGaugeCard; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export type GaugeCardProps = Pick< | ||||
|   ComponentProps<typeof GaugeCard_2>, | ||||
|   'size' | 'alignGauge' | 'variant' | 'description' | 'subheader' | ||||
| >; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export const GaugeGrid: ManageGaugeGrid; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export type GetColumnFunc = (entities: Entity[]) => ManageColumn; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export type GetColumnsFunc = (entities: Entity[]) => ManageColumn[]; | ||||
| 
 | ||||
| // @public | ||||
| export function isManageColumnModuleMultiple( | ||||
|   column: ManageColumnModule, | ||||
| ): column is ManageColumnModuleMultiple; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export interface ItemWithKey<T> { | ||||
|   // (undocumented) | ||||
|   key: string; | ||||
|   // (undocumented) | ||||
|   value: T; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export const KindOrderProvider: (props: { | ||||
|   children?: ReactNode; | ||||
| }) => JSX.Element; | ||||
| 
 | ||||
| // @public | ||||
| export const KindStarred: unique symbol; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export type KindStarredType = typeof KindStarred; | ||||
| 
 | ||||
| // @public | ||||
| export function ManageAccordion( | ||||
|   props: PropsWithChildren<ManageAccordionProps>, | ||||
| ): React_2.JSX.Element; | ||||
| 
 | ||||
| // @public | ||||
| export interface ManageAccordionProps { | ||||
|   defaultExpanded?: boolean; | ||||
|   name: string; | ||||
|   perKind?: boolean; | ||||
|   title: string; | ||||
| } | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export interface ManageApi { | ||||
|   getProviders(): Iterable<ManageProvider>; | ||||
|   readonly kindOrder: string[]; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export const manageApiRef: ApiRef<ManageApi>; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export interface ManageColumn { | ||||
|   // (undocumented) | ||||
|   id: string; | ||||
|   // (undocumented) | ||||
|   render: (opts: { entity: Entity }) => ReactNode; | ||||
|   // (undocumented) | ||||
|   title: string; | ||||
| } | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export type ManageColumnModule = | ||||
|   | ManageColumnModuleMultiple | ||||
|   | ManageColumnModuleSingle; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export interface ManageColumnModuleMultiple { | ||||
|   // (undocumented) | ||||
|   getColumn?: never; | ||||
|   // (undocumented) | ||||
|   getColumns: GetColumnsFunc; | ||||
| } | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export interface ManageColumnModuleSingle { | ||||
|   // (undocumented) | ||||
|   getColumn: GetColumnFunc; | ||||
|   // (undocumented) | ||||
|   getColumns?: never; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export function ManageGaugeCard( | ||||
|   props: ManageGaugeCardProps, | ||||
| ): React_2.JSX.Element; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export interface ManageGaugeCardProps { | ||||
|   gaugeCardProps?: GaugeCardProps; | ||||
|   getColor: GaugePropsGetColor; | ||||
|   progress: number; | ||||
|   title: ReactNode; | ||||
| } | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export function ManageGaugeGrid( | ||||
|   props: ManageGaugeGridProps, | ||||
| ): React_2.JSX.Element; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export interface ManageGaugeGridProps { | ||||
|   // (undocumented) | ||||
|   containerProps?: Pick< | ||||
|     GridOwnProps, | ||||
|     | 'classes' | ||||
|     | 'columns' | ||||
|     | 'columnSpacing' | ||||
|     | 'direction' | ||||
|     | 'rowSpacing' | ||||
|     | 'spacing' | ||||
|     | 'sx' | ||||
|     | 'wrap' | ||||
|     | 'zeroMinWidth' | ||||
|   >; | ||||
|   getColor: (percent: number) => string; | ||||
|   items: { | ||||
|     title: ReactNode; | ||||
|     description?: ReactNode; | ||||
|     progress: number; | ||||
|   }[]; | ||||
|   noBottomMargin?: boolean; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export interface ManageModuleApi { | ||||
|   getProvider?: () => ManageProvider; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export type ManageModuleApiRef = ApiRef<ManageModuleApi>; | ||||
| 
 | ||||
| // @public | ||||
| export function ManageOwnedProvider( | ||||
|   props: PropsWithChildren<OwnedEntitiesProviderProps>, | ||||
| ): React_2.JSX.Element; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export type ManageProvider = ComponentType<{ | ||||
|   children?: ReactNode | undefined; | ||||
| }>; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export const manageReactPlugin: BackstagePlugin<{}, {}, {}>; | ||||
| 
 | ||||
| // @public | ||||
| export function ManageReorderableTabs( | ||||
|   props: ReorderableTabsProps, | ||||
| ): React_2.JSX.Element; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export function ManageTabContentFullHeight({ | ||||
|   children, | ||||
|   bottomMargin, | ||||
|   resizeChild, | ||||
| }: PropsWithChildren<TabContentFullHeightProps>): React_2.JSX.Element; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export interface OwnedEntitiesProviderProps { | ||||
|   // (undocumented) | ||||
|   kinds?: string[]; | ||||
| } | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export const OwnedProvider: ManageOwnedProvider; | ||||
| 
 | ||||
| // @public | ||||
| export interface Owners { | ||||
|   // (undocumented) | ||||
|   groups: Entity[]; | ||||
|   // (undocumented) | ||||
|   ownedEntityRefs: string[]; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export function pluralizeKind(kind: string): string; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export type ProgressColor = Extract<CircularProgressProps['color'], string>; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export const ReorderableTabs: ManageReorderableTabs; | ||||
| 
 | ||||
| // @public | ||||
| export interface ReorderableTabsProps { | ||||
|   // (undocumented) | ||||
|   onChange?: (idOrder: string[]) => void; | ||||
|   // (undocumented) | ||||
|   tabs: { | ||||
|     id: string; | ||||
|     title: string; | ||||
|   }[]; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export function simplifyColumns( | ||||
|   column: ManageColumnModule, | ||||
| ): ManageColumnModuleMultiple; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export const TabContentFullHeight: ManageTabContentFullHeight; | ||||
| 
 | ||||
| // @public | ||||
| export interface TabContentFullHeightProps { | ||||
|   bottomMargin?: number; | ||||
|   resizeChild?: boolean; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export function useAccordionKey(key: string, uniquePerKind?: boolean): string; | ||||
| 
 | ||||
| // @public | ||||
| export function useCurrentKind(): string | KindStarredType | undefined; | ||||
| 
 | ||||
| // @public | ||||
| export function useCurrentKinds( | ||||
|   onlyOwned?: boolean, | ||||
| ): (string | KindStarredType)[]; | ||||
| 
 | ||||
| // @public | ||||
| export function useCurrentKindTitle(): string; | ||||
| 
 | ||||
| // @public | ||||
| export function useKindOrder<T extends string | KindStarredType>( | ||||
|   kinds: T[], | ||||
| ): T[]; | ||||
| 
 | ||||
| // @public | ||||
| export function useManagedEntities(): Entity[]; | ||||
| 
 | ||||
| // @public | ||||
| export function useOrder<T, U>( | ||||
|   items: T[], | ||||
|   keys: U[], | ||||
|   options: UseOrderOptions<T, U>, | ||||
| ): T[]; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export function useOrder<T extends string>( | ||||
|   items: T[], | ||||
|   keys: T[], | ||||
|   options?: Partial<UseOrderOptions<T, T>>, | ||||
| ): T[]; | ||||
| 
 | ||||
| // @public | ||||
| export interface UseOrderOptions<T, U> { | ||||
|   caseSensitive?: boolean; | ||||
|   itemsMemoMethod?: 'reference' | 'key'; | ||||
|   joiner?: (keys: string[]) => string; | ||||
|   keyOf: (item: T) => U; | ||||
|   keysMemoMethod?: 'reference' | 'key'; | ||||
|   nonFoundCompare?: (a: ItemWithKey<T>, b: ItemWithKey<T>) => number; | ||||
|   stringifyItem?: (item: T) => string; | ||||
|   stringifyKey?: (key: U) => string; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export function useOwnedEntities( | ||||
|   kind?: string | KindStarredType | (string | KindStarredType)[], | ||||
| ): Entity[]; | ||||
| 
 | ||||
| // @public | ||||
| export function useOwnedKinds(onlyOwned?: boolean): string[]; | ||||
| 
 | ||||
| // @public | ||||
| export function useOwners(): Owners; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| export interface UserSettingsContextResult<T extends JsonValue> { | ||||
|   // (undocumented) | ||||
|   Provider: (props: PropsWithChildren<{}>) => JSX.Element; | ||||
|   // (undocumented) | ||||
|   useSetSetting: () => (value: T) => void; | ||||
|   // (undocumented) | ||||
|   useSetting: () => T | undefined; | ||||
| } | ||||
| 
 | ||||
| // @public | ||||
| export const useSetKindOrder: () => (value: string[]) => void; | ||||
| 
 | ||||
| // @public | ||||
| export function useUserSettings<T extends JsonValue>( | ||||
|   feature: string, | ||||
|   key: string, | ||||
|   options?: UseUserSettingsOptions<T>, | ||||
| ): [value: T | undefined, setValue: (value: T) => void, isSettled: boolean]; | ||||
| 
 | ||||
| // @public | ||||
| export interface UseUserSettingsOptions<T extends JsonValue> { | ||||
|   coerce?: (value: JsonValue) => T; | ||||
|   // (undocumented) | ||||
|   defaultValue?: T | undefined; | ||||
| } | ||||
| 
 | ||||
| // (No @packageDocumentation comment for this package) | ||||
| ``` | ||||
|  | @ -0,0 +1,53 @@ | |||
| /* | ||||
|  * 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 type { ManageApi, ManageProvider } from './ManageApi'; | ||||
| import { ManageModuleApi } from './types'; | ||||
| 
 | ||||
| /** @public */ | ||||
| export interface DefaultManageApiOptions { | ||||
|   /** | ||||
|    * The kind order to use when rendering the owned entities. | ||||
|    */ | ||||
|   kindOrder?: string[]; | ||||
| 
 | ||||
|   /** | ||||
|    * Manage providers to include. These will be mounted top-level, so that any | ||||
|    * component in the Manage page can access them | ||||
|    */ | ||||
|   providers: Iterable<ManageModuleApi>; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Default implementation of the ManageApi. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export class DefaultManageApi implements ManageApi { | ||||
|   public readonly kindOrder: string[]; | ||||
|   readonly #providers: ManageProvider[] = []; | ||||
| 
 | ||||
|   public constructor({ kindOrder, providers }: DefaultManageApiOptions) { | ||||
|     this.kindOrder = kindOrder ?? []; | ||||
| 
 | ||||
|     this.#providers = Array.from(providers) | ||||
|       .map(provider => provider.getProvider?.()) | ||||
|       .filter((v): v is NonNullable<typeof v> => !!v); | ||||
|   } | ||||
| 
 | ||||
|   getProviders = (): readonly ManageProvider[] => { | ||||
|     return this.#providers; | ||||
|   }; | ||||
| } | ||||
|  | @ -0,0 +1,36 @@ | |||
| /* | ||||
|  * 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 type { ComponentType, ReactNode } from 'react'; | ||||
| 
 | ||||
| /** @public */ | ||||
| export type ManageProvider = ComponentType<{ | ||||
|   children?: ReactNode | undefined; | ||||
| }>; | ||||
| 
 | ||||
| /** @public */ | ||||
| export interface ManageApi { | ||||
|   /** | ||||
|    * The order of kinds to show for e.g. tabs. | ||||
|    * | ||||
|    * Kinds not part of this list will appear afterwards. | ||||
|    */ | ||||
|   readonly kindOrder: string[]; | ||||
| 
 | ||||
|   /** | ||||
|    * Get the list of registered Providers for the manage page | ||||
|    */ | ||||
|   getProviders(): Iterable<ManageProvider>; | ||||
| } | ||||
|  | @ -0,0 +1,70 @@ | |||
| /* | ||||
|  * 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 { createApiFactory, createApiRef } from '@backstage/core-plugin-api'; | ||||
| 
 | ||||
| import type { ManageApi } from './ManageApi'; | ||||
| import { DefaultManageApi } from './DefaultManageApi'; | ||||
| import { ManageModuleApiRef } from './types'; | ||||
| 
 | ||||
| /** | ||||
|  * ApiRef for the Manage API. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export const manageApiRef = createApiRef<ManageApi>({ id: 'manage' }); | ||||
| 
 | ||||
| /** | ||||
|  * Options for creating the Manage API. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export interface ApiFactoryOptions { | ||||
|   /** | ||||
|    * The kind order to use when rendering the owned entities. | ||||
|    */ | ||||
|   kindOrder?: string[]; | ||||
| 
 | ||||
|   /** | ||||
|    * Optional Manage extensions to include in the API. | ||||
|    */ | ||||
|   extensions?: Iterable<ManageModuleApiRef>; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Default API factory for the Manage plugin. | ||||
|  * | ||||
|  * This simplifies the API creation by providing a default implementation. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export function createManageApiFactory(options?: ApiFactoryOptions) { | ||||
|   const { kindOrder, extensions = [] } = options ?? {}; | ||||
| 
 | ||||
|   const apiDeps = Object.fromEntries( | ||||
|     Array.from(extensions).map(apiRef => [apiRef.id, apiRef]), | ||||
|   ); | ||||
| 
 | ||||
|   return createApiFactory({ | ||||
|     api: manageApiRef, | ||||
|     deps: apiDeps, | ||||
|     factory(deps) { | ||||
|       return new DefaultManageApi({ | ||||
|         kindOrder, | ||||
|         providers: Object.values(deps), | ||||
|       }); | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
|  | @ -0,0 +1,24 @@ | |||
| /* | ||||
|  * Copyright 2025 The Backstage Authors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| export type { ManageProvider, ManageApi } from './ManageApi'; | ||||
| 
 | ||||
| export type { ManageModuleApi, ManageModuleApiRef } from './types'; | ||||
| 
 | ||||
| export type { DefaultManageApiOptions } from './DefaultManageApi'; | ||||
| export { DefaultManageApi } from './DefaultManageApi'; | ||||
| 
 | ||||
| export type { ApiFactoryOptions } from './api'; | ||||
| export { manageApiRef, createManageApiFactory } from './api'; | ||||
|  | @ -0,0 +1,38 @@ | |||
| /* | ||||
|  * 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 { ApiRef } from '@backstage/core-plugin-api'; | ||||
| 
 | ||||
| import { ManageProvider } from './ManageApi'; | ||||
| 
 | ||||
| /** | ||||
|  * This is a base interface for plugin modules to extends, to register | ||||
|  * themselves as extensions to the Manage plugin. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export interface ManageModuleApi { | ||||
|   /** | ||||
|    * Get an optional ManageProvider component. | ||||
|    */ | ||||
|   getProvider?: () => ManageProvider; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Base type for extension APIs. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export type ManageModuleApiRef = ApiRef<ManageModuleApi>; | ||||
|  | @ -0,0 +1,118 @@ | |||
| /* | ||||
|  * 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 React, { ComponentProps, PropsWithChildren, useCallback } from 'react'; | ||||
| 
 | ||||
| import { makeStyles } from '@mui/styles'; | ||||
| import Accordion from '@mui/material/Accordion'; | ||||
| import AccordionSummary from '@mui/material/AccordionSummary'; | ||||
| import AccordionDetails from '@mui/material/AccordionDetails'; | ||||
| import Typography from '@mui/material/Typography'; | ||||
| import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; | ||||
| 
 | ||||
| import { useAccordionKey, useUserSettings } from '../../hooks'; | ||||
| 
 | ||||
| const useStyles = makeStyles(theme => ({ | ||||
|   root: { | ||||
|     marginBottom: theme.spacing(2), | ||||
|   }, | ||||
|   summary: { | ||||
|     marginTop: theme.spacing(0.5), | ||||
|     marginBottom: theme.spacing(0), | ||||
|     '&.Mui-expanded': { | ||||
|       marginTop: theme.spacing(0.5), | ||||
|       marginBottom: theme.spacing(1), | ||||
|     }, | ||||
|   }, | ||||
|   details: { | ||||
|     paddingBottom: 0, | ||||
|   }, | ||||
| })); | ||||
| 
 | ||||
| /** | ||||
|  * Props for {@link ManageAccordion} | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export interface ManageAccordionProps { | ||||
|   /** | ||||
|    * Title of the accordion | ||||
|    */ | ||||
|   title: string; | ||||
| 
 | ||||
|   /** | ||||
|    * Name of the accordion. This will be used to create accordion keys to save | ||||
|    * the open/close state. This is intended to be a feature/plugin name. | ||||
|    */ | ||||
|   name: string; | ||||
| 
 | ||||
|   /** | ||||
|    * Make the accordion default-expanded. Defaults to false. | ||||
|    */ | ||||
|   defaultExpanded?: boolean; | ||||
| 
 | ||||
|   /** | ||||
|    * Saves the expanded state per kind. Defaults to false, meaning the expanded | ||||
|    * state is shared between all kinds. | ||||
|    */ | ||||
|   perKind?: boolean; | ||||
| } | ||||
| 
 | ||||
| type AccordionOnChange = NonNullable< | ||||
|   ComponentProps<typeof Accordion>['onChange'] | ||||
| >; | ||||
| 
 | ||||
| /** | ||||
|  * Renders a MUI Accordion with a title and content. The open/close state of the | ||||
|  * accordion is saved in the user settings. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export function ManageAccordion( | ||||
|   props: PropsWithChildren<ManageAccordionProps>, | ||||
| ) { | ||||
|   const { title, name, defaultExpanded, perKind = false, children } = props; | ||||
| 
 | ||||
|   const accordionKey = useAccordionKey('manage-accordion', perKind); | ||||
| 
 | ||||
|   const [expanded, setExpanded] = useUserSettings(name, accordionKey, { | ||||
|     defaultValue: defaultExpanded ?? false, | ||||
|   }); | ||||
| 
 | ||||
|   const onChange = useCallback<AccordionOnChange>( | ||||
|     (_, value) => { | ||||
|       setExpanded(value); | ||||
|     }, | ||||
|     [setExpanded], | ||||
|   ); | ||||
| 
 | ||||
|   const { root, summary, details } = useStyles(); | ||||
| 
 | ||||
|   return ( | ||||
|     <Accordion classes={{ root }} expanded={expanded} onChange={onChange}> | ||||
|       <AccordionSummary | ||||
|         expandIcon={<ExpandMoreIcon />} | ||||
|         classes={{ content: summary }} | ||||
|       > | ||||
|         <Typography variant="h6" component="span"> | ||||
|           {title} | ||||
|         </Typography> | ||||
|       </AccordionSummary> | ||||
|       <AccordionDetails classes={{ root: details }}> | ||||
|         {children} | ||||
|       </AccordionDetails> | ||||
|     </Accordion> | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,17 @@ | |||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
| export type { ManageAccordionProps } from './Accordion'; | ||||
| export { ManageAccordion } from './Accordion'; | ||||
|  | @ -0,0 +1,116 @@ | |||
| /* | ||||
|  * 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 React, { | ||||
|   createContext, | ||||
|   PropsWithChildren, | ||||
|   useContext, | ||||
|   useMemo, | ||||
| } from 'react'; | ||||
| 
 | ||||
| import { useOwnedKinds } from '../OwnedEntitiesProvider/OwnedEntitiesProvider'; | ||||
| import { pluralizeKind } from '../../utils'; | ||||
| import { KindStarred, KindStarredType } from './types'; | ||||
| 
 | ||||
| interface CurrentKindContext { | ||||
|   kind: string | KindStarredType; | ||||
| } | ||||
| 
 | ||||
| const ctx = createContext<CurrentKindContext>(undefined as any); | ||||
| 
 | ||||
| const { Provider } = ctx; | ||||
| 
 | ||||
| /** | ||||
|  * Provider used by `@backstage-community/plugin-manage`, and shouldn't be used | ||||
|  * elsewhere. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export function CurrentKindProvider( | ||||
|   props: PropsWithChildren< | ||||
|     { kind: string; starred?: never } | { kind?: never; starred: true } | ||||
|   >, | ||||
| ) { | ||||
|   const { kind, starred, children } = props; | ||||
| 
 | ||||
|   const value = useMemo(() => { | ||||
|     if (starred) { | ||||
|       return { kind: KindStarred as KindStarredType }; | ||||
|     } | ||||
|     return { kind }; | ||||
|   }, [kind, starred]); | ||||
| 
 | ||||
|   return <Provider value={value}>{children}</Provider>; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Returns the current kind, i.e. if the component is inside a tab with only | ||||
|  * components, or systems, e.g. | ||||
|  * | ||||
|  * If rendered outside such a tab, returns undefined. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export function useCurrentKind(): string | KindStarredType | undefined { | ||||
|   const context = useContext(ctx); | ||||
|   if (!context) { | ||||
|     return undefined; | ||||
|   } | ||||
|   return context.kind; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Same as {@link useCurrentKind} except if not used inside a kind tab, it | ||||
|  * fallbacks to all owned entity kinds. | ||||
|  * | ||||
|  * @param onlyOwned - Only return kinds for entities actually owned, otherwise | ||||
|  *                    all configured kinds | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export function useCurrentKinds( | ||||
|   onlyOwned = false, | ||||
| ): (string | KindStarredType)[] { | ||||
|   const context = useContext(ctx); | ||||
| 
 | ||||
|   const currentKind = useMemo(() => { | ||||
|     if (!context) { | ||||
|       return undefined; | ||||
|     } | ||||
|     return [context.kind as string | KindStarredType]; | ||||
|   }, [context]); | ||||
| 
 | ||||
|   const ownedKinds = useOwnedKinds(onlyOwned); | ||||
| 
 | ||||
|   return currentKind ?? ownedKinds; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * | ||||
|  * Returns the title for the current kind, e.g. "components" or | ||||
|  * "starred entities". | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export function useCurrentKindTitle() { | ||||
|   const kind = useCurrentKind(); | ||||
|   if (!kind) { | ||||
|     return 'entities'; | ||||
|   } | ||||
|   if (kind === KindStarred) { | ||||
|     return 'starred entities'; | ||||
|   } | ||||
|   return `${pluralizeKind(kind)}`; | ||||
| } | ||||
|  | @ -0,0 +1,25 @@ | |||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
| 
 | ||||
| export { | ||||
|   CurrentKindProvider, | ||||
|   useCurrentKind, | ||||
|   useCurrentKinds, | ||||
|   useCurrentKindTitle, | ||||
| } from './CurrentKindProvider'; | ||||
| 
 | ||||
| export type { KindStarredType } from './types'; | ||||
| export { KindStarred } from './types'; | ||||
|  | @ -0,0 +1,25 @@ | |||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
| 
 | ||||
| /** | ||||
|  * Symbol used to represent starred entities. This is a special "kind". | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export const KindStarred = Symbol('Starred entities'); | ||||
| 
 | ||||
| /** @public */ | ||||
| export type KindStarredType = typeof KindStarred; | ||||
|  | @ -0,0 +1,69 @@ | |||
| /* | ||||
|  * 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 React, { ComponentProps, ReactNode } from 'react'; | ||||
| 
 | ||||
| import { GaugeCard, GaugePropsGetColor } from '@backstage/core-components'; | ||||
| 
 | ||||
| /** @public */ | ||||
| export type GaugeCardProps = Pick< | ||||
|   ComponentProps<typeof GaugeCard>, | ||||
|   'size' | 'alignGauge' | 'variant' | 'description' | 'subheader' | ||||
| >; | ||||
| 
 | ||||
| /** @public */ | ||||
| export interface ManageGaugeCardProps { | ||||
|   /** | ||||
|    * Title of the card | ||||
|    */ | ||||
|   title: ReactNode; | ||||
| 
 | ||||
|   /** | ||||
|    * A number between 0 and 1 defining the progress (0% - 100%) | ||||
|    */ | ||||
|   progress: number; | ||||
| 
 | ||||
|   /** | ||||
|    * Function which turns a value into a color | ||||
|    */ | ||||
|   getColor: GaugePropsGetColor; | ||||
| 
 | ||||
|   /** | ||||
|    * Optional gauge card props | ||||
|    */ | ||||
|   gaugeCardProps?: GaugeCardProps; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * This component is `@backstage/core-component`'s GaugeCard with pre-defined | ||||
|  * defaults. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export function ManageGaugeCard(props: ManageGaugeCardProps) { | ||||
|   const { title, progress, getColor, gaugeCardProps } = props; | ||||
| 
 | ||||
|   return ( | ||||
|     <GaugeCard | ||||
|       size="small" | ||||
|       alignGauge="bottom" | ||||
|       variant="fullHeight" | ||||
|       {...gaugeCardProps} | ||||
|       title={title as string} | ||||
|       progress={progress} | ||||
|       getColor={getColor} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,17 @@ | |||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
| export type { ManageGaugeCardProps, GaugeCardProps } from './GaugeCard'; | ||||
| export { ManageGaugeCard } from './GaugeCard'; | ||||
|  | @ -0,0 +1,131 @@ | |||
| /* | ||||
|  * 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 React, { ReactNode } from 'react'; | ||||
| 
 | ||||
| import { makeStyles } from '@mui/styles'; | ||||
| import Box from '@mui/material/Box'; | ||||
| import Grid, { GridOwnProps } from '@mui/material/Grid'; | ||||
| import Typography from '@mui/material/Typography'; | ||||
| 
 | ||||
| const useStyles = makeStyles(theme => ({ | ||||
|   gridRootWithoutBottomMargin: { | ||||
|     marginBottom: 0, | ||||
|   }, | ||||
|   gridItem: { | ||||
|     overflow: 'visible', | ||||
|   }, | ||||
|   box: { | ||||
|     lineHeight: 0, | ||||
|     borderRadius: theme.shape.borderRadius, | ||||
|     borderWidth: 1, | ||||
|     borderLeftWidth: 3, | ||||
|     borderStyle: 'solid', | ||||
|     borderColor: theme.palette.divider, | ||||
|     backgroundColor: theme.palette.background.paper, | ||||
|     // boxShadow: theme.shadows[2],
 | ||||
|   }, | ||||
|   percentText: { | ||||
|     color: theme.palette.text.secondary, | ||||
|   }, | ||||
| })); | ||||
| 
 | ||||
| /** @public */ | ||||
| export interface ManageGaugeGridProps { | ||||
|   containerProps?: Pick< | ||||
|     GridOwnProps, | ||||
|     | 'classes' | ||||
|     | 'columns' | ||||
|     | 'columnSpacing' | ||||
|     | 'direction' | ||||
|     | 'rowSpacing' | ||||
|     | 'spacing' | ||||
|     | 'sx' | ||||
|     | 'wrap' | ||||
|     | 'zeroMinWidth' | ||||
|   >; | ||||
| 
 | ||||
|   /** | ||||
|    * Items to display in the grid | ||||
|    */ | ||||
|   items: { | ||||
|     /** | ||||
|      * Title of the card | ||||
|      */ | ||||
|     title: ReactNode; | ||||
| 
 | ||||
|     /** | ||||
|      * Description of the item | ||||
|      */ | ||||
|     description?: ReactNode; | ||||
| 
 | ||||
|     /** | ||||
|      * A number between 0 and 1 defining the progress (0% - 100%) | ||||
|      */ | ||||
|     progress: number; | ||||
|   }[]; | ||||
| 
 | ||||
|   /** | ||||
|    * Function which turns a progress number (between 0 and 1) into a color | ||||
|    */ | ||||
|   getColor: (percent: number) => string; | ||||
| 
 | ||||
|   /** | ||||
|    * Optionally disable the bottom margin of the grid | ||||
|    */ | ||||
|   noBottomMargin?: boolean; | ||||
| } | ||||
| 
 | ||||
| /** @public */ | ||||
| export function ManageGaugeGrid(props: ManageGaugeGridProps) { | ||||
|   const { containerProps, items, getColor, noBottomMargin } = props; | ||||
| 
 | ||||
|   const { gridRootWithoutBottomMargin, gridItem, box, percentText } = | ||||
|     useStyles(); | ||||
| 
 | ||||
|   const content = ( | ||||
|     <Grid | ||||
|       columnSpacing={2} | ||||
|       marginBottom={2} | ||||
|       {...containerProps} | ||||
|       className={noBottomMargin ? gridRootWithoutBottomMargin : undefined} | ||||
|       container | ||||
|     > | ||||
|       {items.map(({ title, progress }, i) => { | ||||
|         const value = progress * 100; | ||||
|         const color = getColor(progress); | ||||
| 
 | ||||
|         return ( | ||||
|           <Grid item key={i} padding={0} className={gridItem}> | ||||
|             <div className={box} style={{ borderLeftColor: color }}> | ||||
|               <Grid container spacing={0} padding={1} columnSpacing={1}> | ||||
|                 <Grid item> | ||||
|                   <Typography variant="body2" className={percentText}> | ||||
|                     {Math.round(value)}% | ||||
|                   </Typography> | ||||
|                 </Grid> | ||||
|                 <Grid item alignContent="center"> | ||||
|                   {title} | ||||
|                 </Grid> | ||||
|               </Grid> | ||||
|             </div> | ||||
|           </Grid> | ||||
|         ); | ||||
|       })} | ||||
|     </Grid> | ||||
|   ); | ||||
| 
 | ||||
|   return <Box>{content}</Box>; | ||||
| } | ||||
|  | @ -0,0 +1,17 @@ | |||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
| export type { ManageGaugeGridProps } from './GaugeGrid'; | ||||
| export { ManageGaugeGrid } from './GaugeGrid'; | ||||
|  | @ -0,0 +1,104 @@ | |||
| /* | ||||
|  * Copyright 2025 The Backstage Authors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| import { useMemo } from 'react'; | ||||
| 
 | ||||
| import { useApi } from '@backstage/core-plugin-api'; | ||||
| 
 | ||||
| import type { KindStarredType } from '../CurrentKindProvider'; | ||||
| import { createUserSettingsContext } from '../UserSettingsProvider'; | ||||
| import { manageApiRef } from '../../api'; | ||||
| import { joinKinds, kindToOpaqueString } from '../../utils'; | ||||
| import { useOrder } from '../../hooks/use-order'; | ||||
| 
 | ||||
| const userSettingsFeature = '$manage-page-kind'; | ||||
| const userSettingsKey = 'order'; | ||||
| 
 | ||||
| const coerceStringArray = (arr: any): string[] => { | ||||
|   if (!Array.isArray(arr)) { | ||||
|     return []; | ||||
|   } | ||||
|   return arr.map(value => (typeof value !== 'string' ? `${value}` : value)); | ||||
| }; | ||||
| 
 | ||||
| const userSettingsContext = createUserSettingsContext( | ||||
|   userSettingsFeature, | ||||
|   userSettingsKey, | ||||
|   { | ||||
|     defaultValue: [], | ||||
|     coerce: coerceStringArray, | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
| /** | ||||
|  * This is an internal API and should not be used directly. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export const KindOrderProvider = userSettingsContext.Provider; | ||||
| 
 | ||||
| /** | ||||
|  * This hook is internal and should not be used directly. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export const useSetKindOrder = userSettingsContext.useSetSetting; | ||||
| 
 | ||||
| const useKindOrderUserSetting = userSettingsContext.useSetting; | ||||
| 
 | ||||
| /** | ||||
|  * Re-order kinds to adhere to the configured kind order (case-insensitive), | ||||
|  * i.e. configured in the API. | ||||
|  */ | ||||
| function useKindOrderFromApi( | ||||
|   kinds: (string | KindStarredType)[], | ||||
| ): (string | KindStarredType)[] { | ||||
|   const manageApi = useApi(manageApiRef); | ||||
|   const { kindOrder } = manageApi; | ||||
| 
 | ||||
|   const lcKindOrder = useMemo( | ||||
|     () => kindOrder.map(kind => kind.toLocaleLowerCase('en-US')), | ||||
|     [kindOrder], | ||||
|   ); | ||||
| 
 | ||||
|   return useOrder(kinds, lcKindOrder, { | ||||
|     keyOf: (kind: string | KindStarredType) => | ||||
|       kindToOpaqueString(kind).toLocaleLowerCase('en-US'), | ||||
|     stringifyKey: key => kindToOpaqueString(key), | ||||
|     nonFoundCompare: (a, b) => a.key.localeCompare(b.key), | ||||
|     joiner: joinKinds, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Re-order kinds to adhere to the user settings kind order (case-insensitive) | ||||
|  * while falling back to the order as configured in the API. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export function useKindOrder<T extends string | KindStarredType>( | ||||
|   kinds: T[], | ||||
| ): T[] { | ||||
|   const userSettingsOrder = useKindOrderUserSetting() ?? []; | ||||
|   const apiOrder = useKindOrderFromApi(kinds); | ||||
| 
 | ||||
|   const orderBy = userSettingsOrder.length > 0 ? userSettingsOrder : apiOrder; | ||||
| 
 | ||||
|   return useOrder(kinds, orderBy, { | ||||
|     keyOf: (kind: string | KindStarredType) => | ||||
|       kindToOpaqueString(kind).toLocaleLowerCase('en-US'), | ||||
|     stringifyKey: key => kindToOpaqueString(key), | ||||
|   }); | ||||
| } | ||||
|  | @ -0,0 +1,16 @@ | |||
| /* | ||||
|  * 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. | ||||
|  */ | ||||
| export { KindOrderProvider, useSetKindOrder, useKindOrder } from './KindOrder'; | ||||
|  | @ -0,0 +1,198 @@ | |||
| /* | ||||
|  * 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 React, { | ||||
|   PropsWithChildren, | ||||
|   createContext, | ||||
|   useContext, | ||||
|   useMemo, | ||||
| } from 'react'; | ||||
| 
 | ||||
| import useAsync, { AsyncState } from 'react-use/lib/useAsync'; | ||||
| 
 | ||||
| import { useApi } from '@backstage/core-plugin-api'; | ||||
| import { | ||||
|   catalogApiRef, | ||||
|   useStarredEntities, | ||||
| } from '@backstage/plugin-catalog-react'; | ||||
| import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; | ||||
| import { ErrorPanel, Progress } from '@backstage/core-components'; | ||||
| 
 | ||||
| import { useOwners } from '../OwnedGroupsProvider'; | ||||
| import { useKindOrder } from '../KindOrder'; | ||||
| import { arrayify, joinKinds } from '../../utils'; | ||||
| import { getOwnedEntities } from './catalog'; | ||||
| import { defaultKinds } from './types'; | ||||
| import { | ||||
|   type KindStarredType, | ||||
|   KindStarred, | ||||
| } from '../CurrentKindProvider/types'; | ||||
| 
 | ||||
| interface OwnedEntitiesProviderContext { | ||||
|   kinds: string[]; | ||||
|   asyncEntities: AsyncState<Entity[]>; | ||||
|   entities: Entity[]; | ||||
|   starredEntities: Entity[]; | ||||
| } | ||||
| 
 | ||||
| const ctx = createContext<OwnedEntitiesProviderContext>(undefined as any); | ||||
| const { Provider } = ctx; | ||||
| 
 | ||||
| export interface OwnedEntitiesProviderProps { | ||||
|   kinds?: string[]; | ||||
| } | ||||
| 
 | ||||
| export function OwnedEntitiesProvider( | ||||
|   props: PropsWithChildren<OwnedEntitiesProviderProps>, | ||||
| ) { | ||||
|   const { kinds = defaultKinds } = props; | ||||
| 
 | ||||
|   const { starredEntities: starredEntityRefs } = useStarredEntities(); | ||||
| 
 | ||||
|   const catalogApi = useApi(catalogApiRef); | ||||
|   const owners = useOwners(); | ||||
| 
 | ||||
|   const entities = useAsync(async (): Promise<Entity[]> => { | ||||
|     if (owners.ownedEntityRefs.length === 0) { | ||||
|       return []; | ||||
|     } | ||||
| 
 | ||||
|     return getOwnedEntities(catalogApi, kinds, owners.ownedEntityRefs); | ||||
|   }, [kinds, owners.ownedEntityRefs]); | ||||
| 
 | ||||
|   const starredEntityRefList = Array.from(starredEntityRefs); | ||||
|   const starredEntities = useAsync(async (): Promise<Entity[]> => { | ||||
|     return ( | ||||
|       await catalogApi.getEntitiesByRefs({ | ||||
|         entityRefs: starredEntityRefList, | ||||
|       }) | ||||
|     ).items.filter((v): v is NonNullable<typeof v> => !!v); | ||||
|   }, [starredEntityRefList.join(' $ ')]); | ||||
| 
 | ||||
|   const value = useMemo( | ||||
|     (): OwnedEntitiesProviderContext => ({ | ||||
|       kinds, | ||||
|       asyncEntities: entities, | ||||
|       entities: entities.value ?? [], | ||||
|       starredEntities: starredEntities.value ?? [], | ||||
|     }), | ||||
|     [kinds, entities, starredEntities], | ||||
|   ); | ||||
| 
 | ||||
|   if (value.asyncEntities.loading || starredEntities.loading) { | ||||
|     return <Progress />; | ||||
|   } else if (value.asyncEntities.error) { | ||||
|     return <ErrorPanel error={value.asyncEntities.error} />; | ||||
|   } | ||||
| 
 | ||||
|   return <Provider value={value}>{props.children}</Provider>; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Return all kinds given to <ManagePage> | ||||
|  * | ||||
|  * @param onlyOwned - Only return kinds for entities actually owned, otherwise | ||||
|  * all configured kinds | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export function useOwnedKinds(onlyOwned = false): string[] { | ||||
|   const { kinds, entities } = useContext(ctx); | ||||
| 
 | ||||
|   const ownedEntities = useMemo( | ||||
|     () => | ||||
|       new Set( | ||||
|         !onlyOwned | ||||
|           ? [] | ||||
|           : (entities ?? []).map(entity => | ||||
|               entity.kind.toLocaleLowerCase('en-US'), | ||||
|             ), | ||||
|       ), | ||||
|     [onlyOwned, entities], | ||||
|   ); | ||||
| 
 | ||||
|   return useMemo(() => { | ||||
|     if (!onlyOwned) { | ||||
|       return kinds; | ||||
|     } | ||||
| 
 | ||||
|     return kinds.filter(kind => { | ||||
|       const lcKind = kind.toLocaleLowerCase('en-US'); | ||||
|       return ownedEntities.has(lcKind); | ||||
|     }); | ||||
|   }, [onlyOwned, kinds, ownedEntities]); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Returns all owned entities, possibly filtered by kind. | ||||
|  * | ||||
|  * By default all owned entities are returned, but by passing a kind (or array | ||||
|  * of kinds), only those will be returned. There is a special kind `KindStarred` | ||||
|  * exported by this package, will reflects the starred entities. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export function useOwnedEntities( | ||||
|   kind?: string | KindStarredType | (string | KindStarredType)[], | ||||
| ): Entity[] { | ||||
|   const { kinds: ownedKinds, entities, starredEntities } = useContext(ctx); | ||||
| 
 | ||||
|   const kinds = arrayify(kind ?? ownedKinds); | ||||
|   const orderedKinds = useKindOrder(kinds); | ||||
| 
 | ||||
|   return useMemo( | ||||
|     (): Entity[] => { | ||||
|       const lcKinds = orderedKinds.map(curKind => | ||||
|         typeof curKind === 'symbol' | ||||
|           ? curKind | ||||
|           : curKind?.toLocaleLowerCase('en-US'), | ||||
|       ); | ||||
| 
 | ||||
|       const filteredEntities = (entities ?? []).filter(entity => | ||||
|         lcKinds.includes(entity.kind.toLocaleLowerCase('en-US')), | ||||
|       ); | ||||
|       return lcKinds.flatMap(curKind => | ||||
|         curKind === KindStarred | ||||
|           ? starredEntities | ||||
|           : filteredEntities.filter( | ||||
|               entity => entity.kind.toLocaleLowerCase('en-US') === curKind, | ||||
|             ), | ||||
|       ); | ||||
|     }, | ||||
|     // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||
|     [joinKinds(orderedKinds), entities, starredEntities], | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Returns all managed entites, i.e. owned entities and starred entities. | ||||
|  * | ||||
|  * @public | ||||
|  */ | ||||
| export function useManagedEntities(): Entity[] { | ||||
|   const { entities, starredEntities } = useContext(ctx); | ||||
| 
 | ||||
|   return useMemo((): Entity[] => { | ||||
|     const set = new Set<string>(); | ||||
|     return ([] as Entity[]).concat(entities, starredEntities).filter(entity => { | ||||
|       const entityRef = stringifyEntityRef(entity); | ||||
|       if (set.has(entityRef)) { | ||||
|         return false; | ||||
|       } | ||||
|       set.add(entityRef); | ||||
|       return true; | ||||
|     }); | ||||
|   }, [entities, starredEntities]); | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue