diff --git a/workspaces/cost-management/.changeset/nfs-frontend-alpha.md b/workspaces/cost-management/.changeset/nfs-frontend-alpha.md new file mode 100644 index 00000000000..1d6c8d718dd --- /dev/null +++ b/workspaces/cost-management/.changeset/nfs-frontend-alpha.md @@ -0,0 +1,5 @@ +--- +'@red-hat-developer-hub/plugin-cost-management': minor +--- + +Add a New Frontend System `/alpha` export so the plugin loads in RHDH 2.1, with Optimizations and Cost Management sidebar pages. Legacy PluginRoot exports are unchanged. diff --git a/workspaces/cost-management/AGENTS.md b/workspaces/cost-management/AGENTS.md index 36f50cd35c3..0f16935bec2 100644 --- a/workspaces/cost-management/AGENTS.md +++ b/workspaces/cost-management/AGENTS.md @@ -5,9 +5,11 @@ Run all commands from `workspaces/cost-management/` (the workspace root). - Install: `yarn install` -- Dev (full app + backend, slow): `yarn start` +- Dev (NFS app + full backend, RBAC): `yarn start` +- Dev (legacy app + full backend): `yarn start:legacy` - Dev (plugin hot-reload, fast): `yarn start:dev` -- Dev (frontend plugin only): `yarn start:fe-plugin` +- Dev (NFS frontend plugin only): `yarn start:fe-plugin` +- Dev (legacy frontend plugin only): `yarn start:fe-plugin:legacy` - Dev (backend plugin only): `yarn start:be-plugin` - Build all: `yarn build:all` - Test all (with coverage): `yarn test:all` @@ -33,6 +35,9 @@ workspaces/cost-management/ │ └── cost-management-common/ # Shared types, API clients, permissions │ └── src/generated/ # Auto-generated OpenAPI types — do not hand-edit ├── packages/ # Dev environment shell only (app + backend wiring) +│ ├── app/ # NFS frontend (`yarn start`) +│ ├── app-legacy/ # Legacy frontend (`yarn start:legacy`) +│ └── backend/ # Full backend with RBAC ├── docs/ │ ├── rbac.md # Full RBAC permission reference │ └── dynamic-plugin.md # Dynamic plugin installation guide diff --git a/workspaces/cost-management/docs/dynamic-plugin.md b/workspaces/cost-management/docs/dynamic-plugin.md index 41833aebb51..c0c84af7b38 100644 --- a/workspaces/cost-management/docs/dynamic-plugin.md +++ b/workspaces/cost-management/docs/dynamic-plugin.md @@ -80,3 +80,20 @@ The procedure involves the following steps: > proxy. SSO tokens are obtained internally via OAuth2 `client_credentials` > grant and never exposed to the browser. RBAC filtering is enforced > server-side before data is returned. See [rbac.md](./rbac.md) for details. + +5. **RHDH 2.1 (New Frontend System):** enable the same OCI packages. Do **not** require `dynamicRoutes` / `importName`. Pages are discovered from the plugin `/alpha` export. Optional operator overrides: + + ```yaml + app: + extensions: + - page:cost-management: + config: + title: Optimizations + path: /cost-management/optimizations + - page:cost-management/openshift: + config: + title: Cost Management + path: /cost-management/openshift + ``` + + The sidebar shows two items (**Optimizations** and **Cost Management**). Nested `menuItems.parent` is not available on NFS ([RHDH-specific gaps](https://github.com/redhat-developer/rhdh/blob/main/docs/dynamic-plugins/migrating-config-to-new-frontend-system.md#rhdh-specific-gaps)). Enable NFS on the backend with `APP_CONFIG_app_packageName=app-next` and `ENABLE_STANDARD_MODULE_FEDERATION=true` ([config migration](https://github.com/redhat-developer/rhdh/blob/main/docs/dynamic-plugins/migrating-config-to-new-frontend-system.md)). diff --git a/workspaces/cost-management/package.json b/workspaces/cost-management/package.json index 73b59283998..8a37e756227 100644 --- a/workspaces/cost-management/package.json +++ b/workspaces/cost-management/package.json @@ -6,10 +6,12 @@ "node": "22 || 24" }, "scripts": { - "start": "concurrently -c auto -n \"fe,be\" -p \"{name}:{pid}\" \"yarn start-app\" \"yarn start-backend\"", + "start": "backstage-cli repo start", + "start:legacy": "backstage-cli repo start packages/app-legacy packages/backend", "start-app": "yarn workspace app start", "start-backend": "yarn workspace backend start", "start:fe-plugin": "yarn workspace @red-hat-developer-hub/plugin-cost-management start", + "start:fe-plugin:legacy": "yarn workspace @red-hat-developer-hub/plugin-cost-management start:legacy", "start:be-plugin": "yarn workspace @red-hat-developer-hub/plugin-cost-management-backend start", "start:dev": "concurrently -c auto -n \"fe,be\" -p \"{name}:{pid}\" \"yarn start:fe-plugin\" \"yarn start:be-plugin\"", "tsc": "tsc", diff --git a/workspaces/cost-management/packages/app-legacy/.eslintignore b/workspaces/cost-management/packages/app-legacy/.eslintignore new file mode 100644 index 00000000000..a48cf0de7af --- /dev/null +++ b/workspaces/cost-management/packages/app-legacy/.eslintignore @@ -0,0 +1 @@ +public diff --git a/workspaces/cost-management/packages/app-legacy/.eslintrc.js b/workspaces/cost-management/packages/app-legacy/.eslintrc.js new file mode 100644 index 00000000000..e2a53a6ad28 --- /dev/null +++ b/workspaces/cost-management/packages/app-legacy/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/workspaces/cost-management/packages/app/e2e-tests/README.md b/workspaces/cost-management/packages/app-legacy/e2e-tests/README.md similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/README.md rename to workspaces/cost-management/packages/app-legacy/e2e-tests/README.md diff --git a/workspaces/cost-management/packages/app/e2e-tests/TEST-COVERAGE.md b/workspaces/cost-management/packages/app-legacy/e2e-tests/TEST-COVERAGE.md similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/TEST-COVERAGE.md rename to workspaces/cost-management/packages/app-legacy/e2e-tests/TEST-COVERAGE.md diff --git a/workspaces/cost-management/packages/app/e2e-tests/app.test.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/app.test.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/app.test.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/app.test.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/apply-recommendation.test.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/apply-recommendation.test.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/apply-recommendation.test.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/apply-recommendation.test.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/dark-theme.test.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/dark-theme.test.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/dark-theme.test.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/dark-theme.test.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/fixtures/auth.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/fixtures/auth.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/fixtures/auth.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/fixtures/auth.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/fixtures/optimizationResponses.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/fixtures/optimizationResponses.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/fixtures/optimizationResponses.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/fixtures/optimizationResponses.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/global-setup.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/global-setup.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/global-setup.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/global-setup.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/live-cluster.test.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/live-cluster.test.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/live-cluster.test.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/live-cluster.test.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/marketplace.test.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/marketplace.test.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/marketplace.test.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/marketplace.test.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/navigation.test.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/navigation.test.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/navigation.test.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/navigation.test.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/openshift-cost-management.test.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/openshift-cost-management.test.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/openshift-cost-management.test.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/openshift-cost-management.test.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/optimization.test.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/optimization.test.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/optimization.test.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/optimization.test.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/pages/ResourceOptimizationPage.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/pages/ResourceOptimizationPage.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/pages/ResourceOptimizationPage.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/pages/ResourceOptimizationPage.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/rbac-dynamic-permissions.test.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/rbac-dynamic-permissions.test.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/rbac-dynamic-permissions.test.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/rbac-dynamic-permissions.test.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/rbac.test.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/rbac.test.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/rbac.test.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/rbac.test.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/secure-proxy.test.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/secure-proxy.test.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/secure-proxy.test.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/secure-proxy.test.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/table-and-pagination.test.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/table-and-pagination.test.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/table-and-pagination.test.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/table-and-pagination.test.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/utils/apiUtils.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/utils/apiUtils.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/utils/apiUtils.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/utils/apiUtils.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/utils/devMode.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/utils/devMode.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/utils/devMode.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/utils/devMode.ts diff --git a/workspaces/cost-management/packages/app/e2e-tests/utils/routes.ts b/workspaces/cost-management/packages/app-legacy/e2e-tests/utils/routes.ts similarity index 100% rename from workspaces/cost-management/packages/app/e2e-tests/utils/routes.ts rename to workspaces/cost-management/packages/app-legacy/e2e-tests/utils/routes.ts diff --git a/workspaces/cost-management/packages/app-legacy/package.json b/workspaces/cost-management/packages/app-legacy/package.json new file mode 100644 index 00000000000..9cae7ea8b2c --- /dev/null +++ b/workspaces/cost-management/packages/app-legacy/package.json @@ -0,0 +1,89 @@ +{ + "name": "app-legacy", + "version": "0.0.5", + "private": true, + "bundled": true, + "backstage": { + "role": "frontend" + }, + "repository": { + "type": "git", + "url": "https://github.com/redhat-developer/rhdh-plugins", + "directory": "workspaces/cost-management/packages/app-legacy" + }, + "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-rbac": "1.55.2", + "@backstage-community/plugin-tech-radar": "^0.7.4", + "@backstage/app-defaults": "^1.7.11", + "@backstage/catalog-model": "^1.10.0", + "@backstage/cli": "^0.36.5", + "@backstage/core-app-api": "^1.20.4", + "@backstage/core-components": "^0.18.13", + "@backstage/core-plugin-api": "^1.12.9", + "@backstage/integration-react": "^1.2.21", + "@backstage/plugin-api-docs": "^0.14.4", + "@backstage/plugin-catalog": "^2.0.8", + "@backstage/plugin-catalog-common": "^1.1.10", + "@backstage/plugin-catalog-graph": "^0.6.7", + "@backstage/plugin-catalog-import": "^0.13.17", + "@backstage/plugin-catalog-react": "^3.2.2", + "@backstage/plugin-org": "^0.7.8", + "@backstage/plugin-permission-react": "^0.5.4", + "@backstage/plugin-scaffolder": "^1.38.2", + "@backstage/plugin-search": "^1.7.7", + "@backstage/plugin-search-react": "^1.11.7", + "@backstage/plugin-techdocs": "^1.18.0", + "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.39", + "@backstage/plugin-techdocs-react": "^1.3.14", + "@backstage/plugin-user-settings": "^0.9.6", + "@backstage/theme": "^0.7.3", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1", + "@patternfly/patternfly": "^6.3.0", + "@red-hat-developer-hub/backstage-plugin-theme": "^0.14.0", + "@red-hat-developer-hub/plugin-cost-management": "workspace:^", + "history": "^5.0.0", + "react": "^18.0.2", + "react-dom": "^18.0.2", + "react-router": "^6.30.2", + "react-router-dom": "^6.30.2", + "react-use": "^17.2.4" + }, + "devDependencies": { + "@backstage/test-utils": "^1.7.21", + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@mui/icons-material": "^5.16.1", + "@mui/material": "^5.16.1", + "@playwright/test": "1.61.1", + "@testing-library/dom": "^10.0.0", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^16.0.0", + "@testing-library/user-event": "^14.0.0", + "@types/react": "^18", + "@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" + ] +} diff --git a/workspaces/cost-management/packages/app-legacy/public/android-chrome-192x192.png b/workspaces/cost-management/packages/app-legacy/public/android-chrome-192x192.png new file mode 100644 index 00000000000..eec0ae25b97 Binary files /dev/null and b/workspaces/cost-management/packages/app-legacy/public/android-chrome-192x192.png differ diff --git a/workspaces/cost-management/packages/app-legacy/public/apple-touch-icon.png b/workspaces/cost-management/packages/app-legacy/public/apple-touch-icon.png new file mode 100644 index 00000000000..3158830ac77 Binary files /dev/null and b/workspaces/cost-management/packages/app-legacy/public/apple-touch-icon.png differ diff --git a/workspaces/cost-management/packages/app-legacy/public/favicon-16x16.png b/workspaces/cost-management/packages/app-legacy/public/favicon-16x16.png new file mode 100644 index 00000000000..58cf61a35eb Binary files /dev/null and b/workspaces/cost-management/packages/app-legacy/public/favicon-16x16.png differ diff --git a/workspaces/cost-management/packages/app-legacy/public/favicon-32x32.png b/workspaces/cost-management/packages/app-legacy/public/favicon-32x32.png new file mode 100644 index 00000000000..c0915ece759 Binary files /dev/null and b/workspaces/cost-management/packages/app-legacy/public/favicon-32x32.png differ diff --git a/workspaces/cost-management/packages/app-legacy/public/favicon.ico b/workspaces/cost-management/packages/app-legacy/public/favicon.ico new file mode 100644 index 00000000000..5e45e5dfbde Binary files /dev/null and b/workspaces/cost-management/packages/app-legacy/public/favicon.ico differ diff --git a/workspaces/cost-management/packages/app-legacy/public/index.html b/workspaces/cost-management/packages/app-legacy/public/index.html new file mode 100644 index 00000000000..18da7c47733 --- /dev/null +++ b/workspaces/cost-management/packages/app-legacy/public/index.html @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + <%= config.getOptionalString('app.title') ?? 'Backstage' %> + + + +
+ + + diff --git a/workspaces/cost-management/packages/app-legacy/public/manifest.json b/workspaces/cost-management/packages/app-legacy/public/manifest.json new file mode 100644 index 00000000000..4a7c1b4ec42 --- /dev/null +++ b/workspaces/cost-management/packages/app-legacy/public/manifest.json @@ -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" +} diff --git a/workspaces/cost-management/packages/app-legacy/public/robots.txt b/workspaces/cost-management/packages/app-legacy/public/robots.txt new file mode 100644 index 00000000000..01b0f9a1073 --- /dev/null +++ b/workspaces/cost-management/packages/app-legacy/public/robots.txt @@ -0,0 +1,2 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * diff --git a/workspaces/cost-management/packages/app-legacy/public/safari-pinned-tab.svg b/workspaces/cost-management/packages/app-legacy/public/safari-pinned-tab.svg new file mode 100644 index 00000000000..0f500b30023 --- /dev/null +++ b/workspaces/cost-management/packages/app-legacy/public/safari-pinned-tab.svg @@ -0,0 +1 @@ +Created by potrace 1.11, written by Peter Selinger 2001-2013 \ No newline at end of file diff --git a/workspaces/cost-management/packages/app-legacy/src/App.test.tsx b/workspaces/cost-management/packages/app-legacy/src/App.test.tsx new file mode 100644 index 00000000000..452cbf840ac --- /dev/null +++ b/workspaces/cost-management/packages/app-legacy/src/App.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { 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(); + + await waitFor(() => { + expect(rendered.baseElement).toBeInTheDocument(); + }); + }); +}); diff --git a/workspaces/cost-management/packages/app-legacy/src/App.tsx b/workspaces/cost-management/packages/app-legacy/src/App.tsx new file mode 100644 index 00000000000..cabb5c96a1a --- /dev/null +++ b/workspaces/cost-management/packages/app-legacy/src/App.tsx @@ -0,0 +1,157 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { 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 { + 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 { + 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 { + ResourceOptimizationPage, + OpenShiftPage, +} from '@red-hat-developer-hub/plugin-cost-management'; +import { useRhdhTheme } from './hooks/useRhdhTheme'; +import { githubAuthApiRef } from '@backstage/core-plugin-api'; +import { RbacPage } from '@backstage-community/plugin-rbac'; +import '@patternfly/patternfly/patternfly.css'; +import '@patternfly/patternfly/patternfly-charts.css'; + +const options: Parameters[0] = { + apis, + 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 => ( + + ), + }, +}; + +// eslint-disable-next-line react-hooks/rules-of-hooks +const rhdhTheme = useRhdhTheme(); +if (rhdhTheme !== null) { + options.themes = rhdhTheme.themes; +} + +const app = createApp(options); + +const routes = ( + + } /> + } /> + } + > + {entityPage} + + } /> + } + > + + + + + } /> + } /> + + + + } + /> + }> + {searchPage} + + } /> + } /> + } /> + } /> + } /> + +); + +export default app.createRoot( + <> + + + + {routes} + + , +); diff --git a/workspaces/cost-management/packages/app/src/apis.ts b/workspaces/cost-management/packages/app-legacy/src/apis.ts similarity index 100% rename from workspaces/cost-management/packages/app/src/apis.ts rename to workspaces/cost-management/packages/app-legacy/src/apis.ts diff --git a/workspaces/cost-management/packages/app/src/components/Root/LogoFull.tsx b/workspaces/cost-management/packages/app-legacy/src/components/Root/LogoFull.tsx similarity index 100% rename from workspaces/cost-management/packages/app/src/components/Root/LogoFull.tsx rename to workspaces/cost-management/packages/app-legacy/src/components/Root/LogoFull.tsx diff --git a/workspaces/cost-management/packages/app/src/components/Root/LogoIcon.tsx b/workspaces/cost-management/packages/app-legacy/src/components/Root/LogoIcon.tsx similarity index 100% rename from workspaces/cost-management/packages/app/src/components/Root/LogoIcon.tsx rename to workspaces/cost-management/packages/app-legacy/src/components/Root/LogoIcon.tsx diff --git a/workspaces/cost-management/packages/app/src/components/Root/Root.tsx b/workspaces/cost-management/packages/app-legacy/src/components/Root/Root.tsx similarity index 100% rename from workspaces/cost-management/packages/app/src/components/Root/Root.tsx rename to workspaces/cost-management/packages/app-legacy/src/components/Root/Root.tsx diff --git a/workspaces/cost-management/packages/app/src/components/Root/index.ts b/workspaces/cost-management/packages/app-legacy/src/components/Root/index.ts similarity index 100% rename from workspaces/cost-management/packages/app/src/components/Root/index.ts rename to workspaces/cost-management/packages/app-legacy/src/components/Root/index.ts diff --git a/workspaces/cost-management/packages/app/src/components/catalog/EntityPage.tsx b/workspaces/cost-management/packages/app-legacy/src/components/catalog/EntityPage.tsx similarity index 100% rename from workspaces/cost-management/packages/app/src/components/catalog/EntityPage.tsx rename to workspaces/cost-management/packages/app-legacy/src/components/catalog/EntityPage.tsx diff --git a/workspaces/cost-management/packages/app/src/components/rhdh-logo/RhdhLogoFull.tsx b/workspaces/cost-management/packages/app-legacy/src/components/rhdh-logo/RhdhLogoFull.tsx similarity index 100% rename from workspaces/cost-management/packages/app/src/components/rhdh-logo/RhdhLogoFull.tsx rename to workspaces/cost-management/packages/app-legacy/src/components/rhdh-logo/RhdhLogoFull.tsx diff --git a/workspaces/cost-management/packages/app/src/components/rhdh-logo/RhdhLogoIcon.tsx b/workspaces/cost-management/packages/app-legacy/src/components/rhdh-logo/RhdhLogoIcon.tsx similarity index 100% rename from workspaces/cost-management/packages/app/src/components/rhdh-logo/RhdhLogoIcon.tsx rename to workspaces/cost-management/packages/app-legacy/src/components/rhdh-logo/RhdhLogoIcon.tsx diff --git a/workspaces/cost-management/packages/app/src/components/search/SearchPage.tsx b/workspaces/cost-management/packages/app-legacy/src/components/search/SearchPage.tsx similarity index 100% rename from workspaces/cost-management/packages/app/src/components/search/SearchPage.tsx rename to workspaces/cost-management/packages/app-legacy/src/components/search/SearchPage.tsx diff --git a/workspaces/cost-management/packages/app/src/hooks/useRhdhTheme.ts b/workspaces/cost-management/packages/app-legacy/src/hooks/useRhdhTheme.ts similarity index 100% rename from workspaces/cost-management/packages/app/src/hooks/useRhdhTheme.ts rename to workspaces/cost-management/packages/app-legacy/src/hooks/useRhdhTheme.ts diff --git a/workspaces/cost-management/packages/app-legacy/src/index.tsx b/workspaces/cost-management/packages/app-legacy/src/index.tsx new file mode 100644 index 00000000000..703b06bae48 --- /dev/null +++ b/workspaces/cost-management/packages/app-legacy/src/index.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 ReactDOM from 'react-dom/client'; + +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')!).render(); diff --git a/workspaces/cost-management/packages/app-legacy/src/setupTests.ts b/workspaces/cost-management/packages/app-legacy/src/setupTests.ts new file mode 100644 index 00000000000..e6c3ea992e8 --- /dev/null +++ b/workspaces/cost-management/packages/app-legacy/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright Red Hat, Inc. + * + * 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'; diff --git a/workspaces/cost-management/packages/app/package.json b/workspaces/cost-management/packages/app/package.json index d3b71b293ff..aaa9d1f32e1 100644 --- a/workspaces/cost-management/packages/app/package.json +++ b/workspaces/cost-management/packages/app/package.json @@ -20,54 +20,33 @@ }, "dependencies": { "@backstage-community/plugin-rbac": "1.55.2", - "@backstage-community/plugin-tech-radar": "^0.7.4", - "@backstage/app-defaults": "^1.7.11", - "@backstage/catalog-model": "^1.10.0", "@backstage/cli": "^0.36.5", - "@backstage/core-app-api": "^1.20.4", "@backstage/core-components": "^0.18.13", "@backstage/core-plugin-api": "^1.12.9", + "@backstage/frontend-defaults": "^0.5.5", + "@backstage/frontend-plugin-api": "^0.18.0", "@backstage/integration-react": "^1.2.21", - "@backstage/plugin-api-docs": "^0.14.4", + "@backstage/plugin-app-react": "^0.2.6", "@backstage/plugin-catalog": "^2.0.8", - "@backstage/plugin-catalog-common": "^1.1.10", - "@backstage/plugin-catalog-graph": "^0.6.7", - "@backstage/plugin-catalog-import": "^0.13.17", - "@backstage/plugin-catalog-react": "^3.2.2", - "@backstage/plugin-org": "^0.7.8", - "@backstage/plugin-permission-react": "^0.5.4", - "@backstage/plugin-scaffolder": "^1.38.2", - "@backstage/plugin-search": "^1.7.7", - "@backstage/plugin-search-react": "^1.11.7", - "@backstage/plugin-techdocs": "^1.18.0", - "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.39", - "@backstage/plugin-techdocs-react": "^1.3.14", "@backstage/plugin-user-settings": "^0.9.6", "@backstage/theme": "^0.7.3", - "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", - "@patternfly/patternfly": "^6.3.0", - "@red-hat-developer-hub/backstage-plugin-theme": "^0.14.0", + "@backstage/ui": "^0.17.1", + "@mui/icons-material": "^5.17.1", + "@mui/material": "^5.17.1", + "@red-hat-developer-hub/backstage-plugin-theme": "^0.14.11", "@red-hat-developer-hub/plugin-cost-management": "workspace:^", - "history": "^5.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", "react-router": "^6.30.2", - "react-router-dom": "^6.30.2", - "react-use": "^17.2.4" + "react-router-dom": "^6.30.2" }, "devDependencies": { "@backstage/test-utils": "^1.7.21", - "@emotion/react": "^11.11.4", - "@emotion/styled": "^11.11.5", - "@mui/icons-material": "^5.16.1", - "@mui/material": "^5.16.1", "@playwright/test": "1.61.1", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", - "@types/react": "^18", "@types/react-dom": "*", "cross-env": "^7.0.0" }, diff --git a/workspaces/cost-management/packages/app/src/App.test.tsx b/workspaces/cost-management/packages/app/src/App.test.tsx index 452cbf840ac..176b12a1517 100644 --- a/workspaces/cost-management/packages/app/src/App.test.tsx +++ b/workspaces/cost-management/packages/app/src/App.test.tsx @@ -35,7 +35,7 @@ describe('App', () => { ] as any, }; - const rendered = render(); + const rendered = render(App.createRoot()); await waitFor(() => { expect(rendered.baseElement).toBeInTheDocument(); diff --git a/workspaces/cost-management/packages/app/src/App.tsx b/workspaces/cost-management/packages/app/src/App.tsx index cabb5c96a1a..c14d8f010f9 100644 --- a/workspaces/cost-management/packages/app/src/App.tsx +++ b/workspaces/cost-management/packages/app/src/App.tsx @@ -14,144 +14,26 @@ * limitations under the License. */ -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 { - 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 { - 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 { - ResourceOptimizationPage, - OpenShiftPage, -} from '@red-hat-developer-hub/plugin-cost-management'; -import { useRhdhTheme } from './hooks/useRhdhTheme'; -import { githubAuthApiRef } from '@backstage/core-plugin-api'; -import { RbacPage } from '@backstage-community/plugin-rbac'; -import '@patternfly/patternfly/patternfly.css'; -import '@patternfly/patternfly/patternfly-charts.css'; +import rbacPlugin, { + rbacTranslationsModule, +} from '@backstage-community/plugin-rbac/alpha'; +import { createApp } from '@backstage/frontend-defaults'; +import catalogPlugin from '@backstage/plugin-catalog/alpha'; +import userSettingsPlugin from '@backstage/plugin-user-settings/alpha'; +import { rhdhThemeModule } from '@red-hat-developer-hub/backstage-plugin-theme/alpha'; +import costManagementPlugin from '@red-hat-developer-hub/plugin-cost-management/alpha'; +import { navModule } from './modules/nav'; +import { signInModule } from './modules/signIn'; -const options: Parameters[0] = { - apis, - 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 => ( - - ), - }, -}; - -// eslint-disable-next-line react-hooks/rules-of-hooks -const rhdhTheme = useRhdhTheme(); -if (rhdhTheme !== null) { - options.themes = rhdhTheme.themes; -} - -const app = createApp(options); - -const routes = ( - - } /> - } /> - } - > - {entityPage} - - } /> - } - > - - - - - } /> - } /> - - - - } - /> - }> - {searchPage} - - } /> - } /> - } /> - } /> - } /> - -); - -export default app.createRoot( - <> - - - - {routes} - - , -); +export default createApp({ + features: [ + rhdhThemeModule, + navModule, + catalogPlugin, + userSettingsPlugin, + costManagementPlugin, + rbacPlugin, + rbacTranslationsModule, + signInModule, + ], +}); diff --git a/workspaces/cost-management/packages/app/src/index.tsx b/workspaces/cost-management/packages/app/src/index.tsx index 703b06bae48..e2d1ca3d371 100644 --- a/workspaces/cost-management/packages/app/src/index.tsx +++ b/workspaces/cost-management/packages/app/src/index.tsx @@ -16,7 +16,8 @@ import '@backstage/cli/asset-types'; import ReactDOM from 'react-dom/client'; - import App from './App'; +// eslint-disable-next-line @backstage/no-ui-css-imports-in-non-frontend +import '@backstage/ui/css/styles.css'; -ReactDOM.createRoot(document.getElementById('root')!).render(); +ReactDOM.createRoot(document.getElementById('root')!).render(App.createRoot()); diff --git a/workspaces/cost-management/packages/app/src/modules/nav/Sidebar.tsx b/workspaces/cost-management/packages/app/src/modules/nav/Sidebar.tsx new file mode 100644 index 00000000000..9702191ba57 --- /dev/null +++ b/workspaces/cost-management/packages/app/src/modules/nav/Sidebar.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { Administration } from '@backstage-community/plugin-rbac'; +import { + Sidebar, + SidebarDivider, + SidebarGroup, + SidebarItem, + SidebarScrollWrapper, + SidebarSpace, +} from '@backstage/core-components'; +import { NavContentBlueprint } from '@backstage/plugin-app-react'; +import { UserSettingsSignInAvatar } from '@backstage/plugin-user-settings'; +import MenuIcon from '@mui/icons-material/Menu'; + +import { SidebarLogo } from './SidebarLogo'; + +export const SidebarContent = NavContentBlueprint.make({ + params: { + component: ({ navItems }) => { + const nav = navItems.withComponent(item => ( + item.icon} to={item.href} text={item.title} /> + )); + + // Consume so the `/` → `/catalog` redirect is not listed in the sidebar. + nav.take('page:app/home-redirect'); + // Consume page:rbac so it is not always shown. Administration hides + // itself unless getUserAuthorization() returns Authorized (legacy match). + nav.take('page:rbac'); + + return ( + + + + }> + {nav.take('page:catalog')} + {nav.take('page:cost-management')} + {nav.take('page:cost-management/openshift')} + + {nav.rest({ sortBy: 'title' })} + + + + + + } + to="/settings" + > + {nav.take('page:user-settings')} + + + ); + }, + }, +}); diff --git a/workspaces/cost-management/packages/app/src/modules/nav/SidebarLogo.tsx b/workspaces/cost-management/packages/app/src/modules/nav/SidebarLogo.tsx new file mode 100644 index 00000000000..e6c992c0669 --- /dev/null +++ b/workspaces/cost-management/packages/app/src/modules/nav/SidebarLogo.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { + Link, + sidebarConfig, + useSidebarOpenState, +} from '@backstage/core-components'; +import Box from '@mui/material/Box'; +import { + LogoFull, + LogoIcon, +} from '@red-hat-developer-hub/backstage-plugin-theme'; + +export const SidebarLogo = () => { + const { isOpen } = useSidebarOpenState(); + + return ( + + + {isOpen ? : } + + + ); +}; diff --git a/workspaces/cost-management/packages/app/src/modules/nav/homeRedirect.tsx b/workspaces/cost-management/packages/app/src/modules/nav/homeRedirect.tsx new file mode 100644 index 00000000000..bc42d55d195 --- /dev/null +++ b/workspaces/cost-management/packages/app/src/modules/nav/homeRedirect.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { PageBlueprint } from '@backstage/frontend-plugin-api'; +import { Navigate } from 'react-router-dom'; + +/** + * NFS has no default `/` page. Match the legacy app, which redirects `/` to catalog. + */ +export const homeRedirectPage = PageBlueprint.make({ + name: 'home-redirect', + params: { + path: '/', + noHeader: true, + loader: async () => , + }, +}); diff --git a/workspaces/cost-management/packages/app/src/modules/nav/index.ts b/workspaces/cost-management/packages/app/src/modules/nav/index.ts new file mode 100644 index 00000000000..a3e1f522c22 --- /dev/null +++ b/workspaces/cost-management/packages/app/src/modules/nav/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { createFrontendModule } from '@backstage/frontend-plugin-api'; +import { homeRedirectPage } from './homeRedirect'; +import { SidebarContent } from './Sidebar'; + +export const navModule = createFrontendModule({ + pluginId: 'app', + extensions: [homeRedirectPage, SidebarContent], +}); diff --git a/workspaces/cost-management/packages/app/src/modules/signIn/SignInPageComponent.tsx b/workspaces/cost-management/packages/app/src/modules/signIn/SignInPageComponent.tsx new file mode 100644 index 00000000000..6699b30861a --- /dev/null +++ b/workspaces/cost-management/packages/app/src/modules/signIn/SignInPageComponent.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { SignInPage } from '@backstage/core-components'; +import { githubAuthApiRef } from '@backstage/core-plugin-api'; +import type { SignInPageProps } from '@backstage/plugin-app-react'; + +const githubProvider = { + id: 'github-auth-provider', + title: 'GitHub', + message: 'Sign in using GitHub', + apiRef: githubAuthApiRef, +}; + +export function SignInPageComponent(props: SignInPageProps) { + return ; +} diff --git a/workspaces/cost-management/packages/app/src/modules/signIn/index.ts b/workspaces/cost-management/packages/app/src/modules/signIn/index.ts new file mode 100644 index 00000000000..eb252724498 --- /dev/null +++ b/workspaces/cost-management/packages/app/src/modules/signIn/index.ts @@ -0,0 +1,30 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { createFrontendModule } from '@backstage/frontend-plugin-api'; +import { SignInPageBlueprint } from '@backstage/plugin-app-react'; + +export const signInModule = createFrontendModule({ + pluginId: 'app', + extensions: [ + SignInPageBlueprint.make({ + params: { + loader: () => + import('./SignInPageComponent').then(m => m.SignInPageComponent), + }, + }), + ], +}); diff --git a/workspaces/cost-management/playwright.config.ts b/workspaces/cost-management/playwright.config.ts index 9b36e7ca6a8..90edf37f129 100644 --- a/workspaces/cost-management/playwright.config.ts +++ b/workspaces/cost-management/playwright.config.ts @@ -24,7 +24,9 @@ import { defineConfig } from '@playwright/test'; export default defineConfig({ ...(process.env.PLAYWRIGHT_URL ? { - globalSetup: require.resolve('./packages/app/e2e-tests/global-setup'), + globalSetup: require.resolve( + './packages/app-legacy/e2e-tests/global-setup', + ), } : {}), @@ -38,7 +40,7 @@ export default defineConfig({ ? [] : [ { - command: 'yarn start-app', + command: 'yarn workspace app-legacy start', port: 3000, reuseExistingServer: true, timeout: 120_000, @@ -77,7 +79,7 @@ export default defineConfig({ // Merge-gate safe: runs against the local dev server in CI { name: 'chromium', - testDir: 'packages/app/e2e-tests', + testDir: 'packages/app-legacy/e2e-tests', testMatch: /app\.test\.ts$/, use: { channel: 'chrome', @@ -90,7 +92,7 @@ export default defineConfig({ ? [ { name: 'live', - testDir: 'packages/app/e2e-tests', + testDir: 'packages/app-legacy/e2e-tests', testMatch: /^(?!app\.test\.ts$).*\.test\.ts$/, use: { channel: 'chrome' as const, diff --git a/workspaces/cost-management/plugins/cost-management/README.md b/workspaces/cost-management/plugins/cost-management/README.md index 147b387a6e3..0afaec0abfb 100644 --- a/workspaces/cost-management/plugins/cost-management/README.md +++ b/workspaces/cost-management/plugins/cost-management/README.md @@ -44,7 +44,34 @@ You can follow one of these options depending on your environment and how you ch backend.add(import('@red-hat-developer-hub/plugin-cost-management-backend')); ``` -1. Add the `ResourceOptimizationPage` extension to your `App.tsx` routes +1. Add the frontend plugin to your app. + + **New Frontend System** (RHDH 2.1 / `app-next`): + + ```ts + import costManagementPlugin from '@red-hat-developer-hub/plugin-cost-management/alpha'; + + const app = createApp({ + features: [costManagementPlugin], + }); + ``` + + Sidebar items come from the plugin: **Optimizations** (`/cost-management/optimizations`) and **Cost Management** (`/cost-management/openshift`). Optional overrides: + + ```yaml + app: + extensions: + - page:cost-management: + config: + title: Optimizations + path: /cost-management/optimizations + - page:cost-management/openshift: + config: + title: Cost Management + path: /cost-management/openshift + ``` + + **Legacy frontend** (`FlatRoutes`): ```ts // packages/app/src/App.tsx diff --git a/workspaces/cost-management/plugins/cost-management/dev/index.tsx b/workspaces/cost-management/plugins/cost-management/dev/index.tsx index 82cf9f92304..234a3185d9f 100644 --- a/workspaces/cost-management/plugins/cost-management/dev/index.tsx +++ b/workspaces/cost-management/plugins/cost-management/dev/index.tsx @@ -14,26 +14,26 @@ * limitations under the License. */ -import { createDevApp } from '@backstage/dev-utils'; -import { - resourceOptimizationPlugin, - ResourceOptimizationPage, - OpenShiftPage, -} from '../src/plugin'; -import { CostManagementIconOutlined } from '../src/components/icon/CostManagementIconOutlined'; - -createDevApp() - .registerPlugin(resourceOptimizationPlugin) - .addPage({ - title: 'Optimizations', - path: '/cost-management/optimizations', - element: , - icon: CostManagementIconOutlined, - }) - .addPage({ - title: 'OpenShift', - path: '/cost-management/openshift', - element: , - icon: CostManagementIconOutlined, - }) - .render(); +/** + * New Frontend System plugin standalone (yarn start / yarn start:fe-plugin). + * Legacy createDevApp: yarn start:legacy + */ + +import '@backstage/cli/asset-types'; + +import ReactDOM from 'react-dom/client'; +import { createApp } from '@backstage/frontend-defaults'; + +import costManagementPlugin from '../src/alpha'; + +const DEFAULT_PATH = '/cost-management/optimizations'; + +const app = createApp({ + features: [costManagementPlugin], +}); + +if (typeof window !== 'undefined' && window.location.pathname === '/') { + window.location.pathname = DEFAULT_PATH; +} + +ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot()); diff --git a/workspaces/cost-management/plugins/cost-management/dev/legacy.tsx b/workspaces/cost-management/plugins/cost-management/dev/legacy.tsx new file mode 100644 index 00000000000..82cf9f92304 --- /dev/null +++ b/workspaces/cost-management/plugins/cost-management/dev/legacy.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { createDevApp } from '@backstage/dev-utils'; +import { + resourceOptimizationPlugin, + ResourceOptimizationPage, + OpenShiftPage, +} from '../src/plugin'; +import { CostManagementIconOutlined } from '../src/components/icon/CostManagementIconOutlined'; + +createDevApp() + .registerPlugin(resourceOptimizationPlugin) + .addPage({ + title: 'Optimizations', + path: '/cost-management/optimizations', + element: , + icon: CostManagementIconOutlined, + }) + .addPage({ + title: 'OpenShift', + path: '/cost-management/openshift', + element: , + icon: CostManagementIconOutlined, + }) + .render(); diff --git a/workspaces/cost-management/plugins/cost-management/package.json b/workspaces/cost-management/plugins/cost-management/package.json index e95e974bbc1..3ee8b20edf3 100644 --- a/workspaces/cost-management/plugins/cost-management/package.json +++ b/workspaces/cost-management/plugins/cost-management/package.json @@ -14,6 +14,8 @@ "dependencies": { "@backstage/core-components": "^0.18.13", "@backstage/core-plugin-api": "^1.12.9", + "@backstage/frontend-plugin-api": "^0.18.0", + "@backstage/plugin-app-react": "^0.2.6", "@backstage/plugin-permission-common": "^0.9.10", "@backstage/plugin-permission-react": "^0.5.4", "@backstage/theme": "^0.7.3", @@ -49,6 +51,8 @@ "@backstage/cli": "^0.36.5", "@backstage/core-app-api": "^1.20.4", "@backstage/dev-utils": "^1.1.26", + "@backstage/frontend-defaults": "^0.5.5", + "@backstage/frontend-test-utils": "^0.6.3", "@backstage/plugin-search-react": "^1.11.7", "@backstage/test-utils": "^1.7.21", "@testing-library/dom": "^10.0.0", @@ -62,6 +66,7 @@ }, "exports": { ".": "./src/index.ts", + "./alpha": "./src/alpha.tsx", "./package.json": "./package.json" }, "files": [ @@ -90,12 +95,16 @@ "postpack": "backstage-cli package postpack", "prepack": "backstage-cli package prepack", "start": "backstage-cli package start", + "start:legacy": "backstage-cli package start --entrypoint=dev/legacy.tsx", "test": "backstage-cli package test" }, "sideEffects": false, "types": "src/index.ts", "typesVersions": { "*": { + "alpha": [ + "src/alpha.tsx" + ], "package.json": [ "package.json" ] diff --git a/workspaces/cost-management/plugins/cost-management/report-alpha.api.md b/workspaces/cost-management/plugins/cost-management/report-alpha.api.md new file mode 100644 index 00000000000..36f88dfe1d4 --- /dev/null +++ b/workspaces/cost-management/plugins/cost-management/report-alpha.api.md @@ -0,0 +1,251 @@ +## API Report File for "@red-hat-developer-hub/plugin-cost-management" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; +import { JSX as JSX_2 } from 'react'; +import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; +import { PathParams } from '@backstage/core-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; +import { SubRouteRef } from '@backstage/core-plugin-api'; + +// @alpha +const _default: OverridableFrontendPlugin< + { + root: RouteRef; + openShift: RouteRef; + breakdown: SubRouteRef>; + }, + {}, + { + 'api:cost-management/cost-management-slim': OverridableExtensionDefinition<{ + kind: 'api'; + name: 'cost-management-slim'; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; + }>; + 'api:cost-management/optimizations': OverridableExtensionDefinition<{ + kind: 'api'; + name: 'optimizations'; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; + }>; + 'api:cost-management/orchestrator-slim': OverridableExtensionDefinition<{ + kind: 'api'; + name: 'orchestrator-slim'; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; + }>; + 'icon-bundle:cost-management/cost-management-icons': OverridableExtensionDefinition<{ + kind: 'icon-bundle'; + name: 'cost-management-icons'; + config: {}; + configInput: {}; + output: ExtensionDataRef< + { + [x: string]: IconComponent | IconElement; + }, + 'core.icons', + {} + >; + inputs: {}; + params: { + icons: { [key in string]: IconComponent | IconElement }; + }; + }>; + 'page:cost-management': OverridableExtensionDefinition<{ + kind: 'page'; + name: undefined; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; + params: { + path: string; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef_2; + noHeader?: boolean; + }; + }>; + 'page:cost-management/openshift': OverridableExtensionDefinition<{ + kind: 'page'; + name: 'openshift'; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef + | ExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; + params: { + path: string; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef_2; + noHeader?: boolean; + }; + }>; + } +>; +export default _default; + +// (No @packageDocumentation comment for this package) +``` diff --git a/workspaces/cost-management/plugins/cost-management/report.api.md b/workspaces/cost-management/plugins/cost-management/report.api.md index 208035109e5..87eb5d67c41 100644 --- a/workspaces/cost-management/plugins/cost-management/report.api.md +++ b/workspaces/cost-management/plugins/cost-management/report.api.md @@ -55,7 +55,7 @@ export const resourceOptimizationPlugin: BackstagePlugin< {} >; -// @public (undocumented) +// @public export function Router(): JSX_2.Element; // (No @packageDocumentation comment for this package) diff --git a/workspaces/cost-management/plugins/cost-management/src/Router.tsx b/workspaces/cost-management/plugins/cost-management/src/Router.tsx index 5360f7dd949..a94b2dcc955 100644 --- a/workspaces/cost-management/plugins/cost-management/src/Router.tsx +++ b/workspaces/cost-management/plugins/cost-management/src/Router.tsx @@ -18,23 +18,35 @@ import { ErrorPage } from '@backstage/core-components'; import { Routes, Route } from 'react-router-dom'; import { OptimizationsPage } from './pages/optimizations/OptimizationsPage'; import { OptimizationsBreakdownPage } from './pages/optimizations-breakdown/OptimizationsBreakdownPage'; -import { OpenShiftPage } from './pages/openshift/OpenShiftPage'; import { usePatternFlyTheme } from './hooks/usePatternFlyTheme'; -/** @public */ +const notFound = ; + +/** + * NFS Optimizations page loader: list + breakdown only. + * OpenShift is a separate PageBlueprint, not a child of this mount. + */ +export function OptimizationsRouter() { + usePatternFlyTheme(); + + return ( + + } /> + } /> + + + ); +} + +/** @public Legacy ResourceOptimizationPage mount (list + breakdown). */ export function Router() { usePatternFlyTheme(); return ( } /> - } /> - } /> } /> - } - /> + ); } diff --git a/workspaces/cost-management/plugins/cost-management/src/alpha.test.tsx b/workspaces/cost-management/plugins/cost-management/src/alpha.test.tsx new file mode 100644 index 00000000000..c302edcf937 --- /dev/null +++ b/workspaces/cost-management/plugins/cost-management/src/alpha.test.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { coreExtensionData } from '@backstage/frontend-plugin-api'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; + +import plugin from './alpha'; +import { openShiftRouteRef, rootRouteRef } from './routes'; + +describe('cost-management alpha', () => { + it('exports a valid frontend plugin', () => { + expect(plugin).toBeDefined(); + expect(plugin.$$type).toBe('@backstage/FrontendPlugin'); + }); + + it('has plugin id cost-management', () => { + expect(plugin.id).toBe('cost-management'); + }); + + it('exposes root, openShift and breakdown routes', () => { + expect(plugin.routes).toHaveProperty('root'); + expect(plugin.routes).toHaveProperty('openShift'); + expect(plugin.routes).toHaveProperty('breakdown'); + }); + + it('registers pages, APIs and the icon bundle', () => { + expect(plugin.getExtension('page:cost-management')).toBeDefined(); + expect(plugin.getExtension('page:cost-management/openshift')).toBeDefined(); + expect( + plugin.getExtension('api:cost-management/optimizations'), + ).toBeDefined(); + expect( + plugin.getExtension('api:cost-management/orchestrator-slim'), + ).toBeDefined(); + expect( + plugin.getExtension('api:cost-management/cost-management-slim'), + ).toBeDefined(); + expect( + plugin.getExtension('icon-bundle:cost-management/cost-management-icons'), + ).toBeDefined(); + }); +}); + +describe('cost-management NFS wiring', () => { + it('declares Optimizations page path, title and route ref', () => { + const tester = createExtensionTester( + plugin.getExtension('page:cost-management'), + ); + + expect(tester.get(coreExtensionData.routePath)).toBe( + '/cost-management/optimizations', + ); + expect(tester.get(coreExtensionData.title)).toBe('Optimizations'); + expect(tester.get(coreExtensionData.routeRef)).toBe(rootRouteRef); + }); + + it('declares OpenShift page with Cost Management nav title', () => { + const tester = createExtensionTester( + plugin.getExtension('page:cost-management/openshift'), + ); + + expect(tester.get(coreExtensionData.routePath)).toBe( + '/cost-management/openshift', + ); + expect(tester.get(coreExtensionData.title)).toBe('Cost Management'); + expect(tester.get(coreExtensionData.routeRef)).toBe(openShiftRouteRef); + }); +}); diff --git a/workspaces/cost-management/plugins/cost-management/src/alpha.tsx b/workspaces/cost-management/plugins/cost-management/src/alpha.tsx new file mode 100644 index 00000000000..9599e3019d5 --- /dev/null +++ b/workspaces/cost-management/plugins/cost-management/src/alpha.tsx @@ -0,0 +1,163 @@ +/* + * Copyright Red Hat, Inc. + * + * 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. + */ + +/** + * New Frontend System entry (`./alpha`). + * + * @packageDocumentation + */ + +import { + discoveryApiRef, + fetchApiRef, + identityApiRef, +} from '@backstage/core-plugin-api'; +import { + ApiBlueprint, + createApiFactory, + createFrontendPlugin, + PageBlueprint, +} from '@backstage/frontend-plugin-api'; +import { IconBundleBlueprint } from '@backstage/plugin-app-react'; +import { + CostManagementSlimClient, + OptimizationsClient, + OrchestratorSlimClient, +} from '@red-hat-developer-hub/plugin-cost-management-common/clients'; +import { + costManagementSlimApiRef, + optimizationsApiRef, + orchestratorSlimApiRef, +} from './apis'; +import { CostManagementIconOutlined } from './components/icon'; +import { + optimizationsBreakdownRouteRef, + openShiftRouteRef, + rootRouteRef, +} from './routes'; + +const optimizationsApi = ApiBlueprint.make({ + name: 'optimizations', + params: defineParams => + defineParams( + createApiFactory({ + api: optimizationsApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + }, + factory: ({ discoveryApi, fetchApi }) => + new OptimizationsClient({ + discoveryApi, + fetchApi, + }), + }), + ), +}); + +const orchestratorSlimApi = ApiBlueprint.make({ + name: 'orchestrator-slim', + params: defineParams => + defineParams( + createApiFactory({ + api: orchestratorSlimApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + identityApi: identityApiRef, + }, + factory: ({ discoveryApi, fetchApi, identityApi }) => + new OrchestratorSlimClient({ + discoveryApi, + fetchApi, + identityApi, + }), + }), + ), +}); + +const costManagementSlimApi = ApiBlueprint.make({ + name: 'cost-management-slim', + params: defineParams => + defineParams( + createApiFactory({ + api: costManagementSlimApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + }, + factory: ({ discoveryApi, fetchApi }) => + new CostManagementSlimClient({ + discoveryApi, + fetchApi, + }), + }), + ), +}); + +const costManagementIcons = IconBundleBlueprint.make({ + name: 'cost-management-icons', + params: { + icons: { + costManagementIconOutlined: CostManagementIconOutlined, + }, + }, +}); + +const optimizationsPage = PageBlueprint.make({ + params: { + path: '/cost-management/optimizations', + routeRef: rootRouteRef, + title: 'Optimizations', + icon: , + noHeader: true, + loader: () => import('./Router').then(m => ), + }, +}); + +const openShiftPage = PageBlueprint.make({ + name: 'openshift', + params: { + path: '/cost-management/openshift', + routeRef: openShiftRouteRef, + title: 'Cost Management', + icon: , + noHeader: true, + loader: () => + import('./pages/openshift/OpenShiftPage').then(m => ), + }, +}); + +/** + * Cost Management plugin for the Backstage new frontend system. + * @alpha + */ +export default createFrontendPlugin({ + pluginId: 'cost-management', + extensions: [ + optimizationsApi, + orchestratorSlimApi, + costManagementSlimApi, + costManagementIcons, + optimizationsPage, + openShiftPage, + ], + routes: { + root: rootRouteRef, + openShift: openShiftRouteRef, + breakdown: optimizationsBreakdownRouteRef, + }, +}); diff --git a/workspaces/cost-management/plugins/cost-management/src/pages/openshift/OpenShiftPage.tsx b/workspaces/cost-management/plugins/cost-management/src/pages/openshift/OpenShiftPage.tsx index 780f454b238..f452aa9bfa0 100644 --- a/workspaces/cost-management/plugins/cost-management/src/pages/openshift/OpenShiftPage.tsx +++ b/workspaces/cost-management/plugins/cost-management/src/pages/openshift/OpenShiftPage.tsx @@ -31,6 +31,7 @@ import { PageHeader } from './components/PageHeader'; import { TableToolbar } from './components/TableToolbar'; import { useApi } from '@backstage/core-plugin-api'; import { costManagementSlimApiRef } from '../../apis'; +import { usePatternFlyTheme } from '../../hooks/usePatternFlyTheme'; import useAsync from 'react-use/lib/useAsync'; import { CURRENCY_SYMBOLS } from '../../constants/currencies'; import { DownloadIconButton } from './components/DownloadIconButton'; @@ -220,6 +221,7 @@ function buildCostManagementQueryParams( /** @public */ export function OpenShiftPage() { + usePatternFlyTheme(); const api = useApi(costManagementSlimApiRef); const [groupBy, setGroupBy] = useState('project'); const [overheadDistribution, setOverheadDistribution] = diff --git a/workspaces/cost-management/yarn.lock b/workspaces/cost-management/yarn.lock index 0ab3f41d523..e52d02f9fa9 100644 --- a/workspaces/cost-management/yarn.lock +++ b/workspaces/cost-management/yarn.lock @@ -3914,6 +3914,55 @@ __metadata: languageName: node linkType: hard +"@backstage/frontend-app-api@npm:^0.16.7": + version: 0.16.7 + resolution: "@backstage/frontend-app-api@npm:0.16.7" + dependencies: + "@backstage/config": "npm:^1.3.8" + "@backstage/core-app-api": "npm:^1.20.4" + "@backstage/core-plugin-api": "npm:^1.12.9" + "@backstage/errors": "npm:^1.3.1" + "@backstage/filter-predicates": "npm:^0.1.4" + "@backstage/frontend-defaults": "npm:^0.5.5" + "@backstage/frontend-plugin-api": "npm:^0.18.0" + "@backstage/types": "npm:^1.2.2" + "@backstage/version-bridge": "npm:^1.0.12" + lodash: "npm:^4.17.21" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + react-router-dom: ^6.30.2 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/061d4e42d0ca5d55e368111bbdf0d1fea9d44d5e55b16c8ad00605d7a8eee363cca68396e98d0ba1aa2760c2d8bf269712922b147a68580ddb2ccd37a4ef12b2 + languageName: node + linkType: hard + +"@backstage/frontend-defaults@npm:^0.5.5": + version: 0.5.5 + resolution: "@backstage/frontend-defaults@npm:0.5.5" + dependencies: + "@backstage/config": "npm:^1.3.8" + "@backstage/core-components": "npm:^0.18.13" + "@backstage/errors": "npm:^1.3.1" + "@backstage/frontend-app-api": "npm:^0.16.7" + "@backstage/frontend-plugin-api": "npm:^0.18.0" + "@backstage/plugin-app": "npm:^0.5.2" + "@react-hookz/web": "npm:^24.0.0" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + react-router-dom: ^6.30.2 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/ee19823794719c430c263c2f2482f053366bf212f3b51de7cca13712e2786bd016f3b780c8d1ebdbe3b010c1559a3689a55290ae66acd71fbc18b9ecd34acacc + languageName: node + linkType: hard + "@backstage/frontend-plugin-api@npm:^0.15.1": version: 0.15.1 resolution: "@backstage/frontend-plugin-api@npm:0.15.1" @@ -4006,6 +4055,42 @@ __metadata: languageName: node linkType: hard +"@backstage/frontend-test-utils@npm:^0.6.3": + version: 0.6.3 + resolution: "@backstage/frontend-test-utils@npm:0.6.3" + dependencies: + "@backstage/config": "npm:^1.3.8" + "@backstage/core-app-api": "npm:^1.20.4" + "@backstage/core-plugin-api": "npm:^1.12.9" + "@backstage/filter-predicates": "npm:^0.1.4" + "@backstage/frontend-app-api": "npm:^0.16.7" + "@backstage/frontend-plugin-api": "npm:^0.18.0" + "@backstage/plugin-app": "npm:^0.5.2" + "@backstage/plugin-app-react": "npm:^0.2.6" + "@backstage/plugin-permission-common": "npm:^0.9.10" + "@backstage/plugin-permission-react": "npm:^0.5.4" + "@backstage/test-utils": "npm:^1.7.21" + "@backstage/types": "npm:^1.2.2" + "@backstage/version-bridge": "npm:^1.0.12" + i18next: "npm:^22.4.15" + zen-observable: "npm:^0.10.0" + zod: "npm:^3.25.76 || ^4.0.0" + peerDependencies: + "@testing-library/react": ^16.0.0 + "@types/jest": "*" + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + react-router-dom: ^6.30.2 + peerDependenciesMeta: + "@types/jest": + optional: true + "@types/react": + optional: true + checksum: 10c0/276546fc9b2d43e2cac397b8c81c9148db8e2d5fad6d981559dac3013298b3e7d3ad76f9cfe059cb7626a6c6d1745477bbb6fdc619633d38c95e824f610f7edb + languageName: node + linkType: hard + "@backstage/integration-aws-node@npm:^0.2.1": version: 0.2.1 resolution: "@backstage/integration-aws-node@npm:0.2.1" @@ -4179,6 +4264,44 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-app@npm:^0.5.2": + version: 0.5.2 + resolution: "@backstage/plugin-app@npm:0.5.2" + dependencies: + "@backstage/core-components": "npm:^0.18.13" + "@backstage/core-plugin-api": "npm:^1.12.9" + "@backstage/filter-predicates": "npm:^0.1.4" + "@backstage/frontend-plugin-api": "npm:^0.18.0" + "@backstage/integration-react": "npm:^1.2.21" + "@backstage/plugin-app-react": "npm:^0.2.6" + "@backstage/plugin-permission-react": "npm:^0.5.4" + "@backstage/theme": "npm:^0.7.3" + "@backstage/types": "npm:^1.2.2" + "@backstage/ui": "npm:^0.17.1" + "@backstage/version-bridge": "npm:^1.0.12" + "@material-ui/core": "npm:^4.9.13" + "@material-ui/icons": "npm:^4.9.1" + "@material-ui/lab": "npm:^4.0.0-alpha.61" + "@react-hookz/web": "npm:^24.0.0" + "@remixicon/react": "npm:>=4.6.0 <4.9.0" + motion: "npm:^12.0.0" + react-aria: "npm:~3.48.0" + react-stately: "npm:~3.46.0" + react-use: "npm:^17.2.4" + zen-observable: "npm:^0.10.0" + zod: "npm:^4.0.0" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + react-router-dom: ^6.30.2 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/622637256285c0d32e844fa43c25893e39c50fa8d22aa6cda9ee331db13366ddb8cb490a0e6691ed842d65de5d34c21b67299afb6e18df1cbcafce92028b9a76 + languageName: node + linkType: hard + "@backstage/plugin-auth-backend-module-github-provider@npm:^0.5.6": version: 0.5.6 resolution: "@backstage/plugin-auth-backend-module-github-provider@npm:0.5.6" @@ -8036,7 +8159,7 @@ __metadata: languageName: node linkType: hard -"@material-ui/lab@npm:4.0.0-alpha.61": +"@material-ui/lab@npm:4.0.0-alpha.61, @material-ui/lab@npm:^4.0.0-alpha.61": version: 4.0.0-alpha.61 resolution: "@material-ui/lab@npm:4.0.0-alpha.61" dependencies: @@ -8566,10 +8689,10 @@ __metadata: languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^5.17.1": - version: 5.17.1 - resolution: "@mui/core-downloads-tracker@npm:5.17.1" - checksum: 10c0/c36641e274a27cdef8a75218021a5ebef0ce588cd0aec004802d56e8d59ac9d8603b8b4840df480de40ce2c3af8898d49929a8564ed5a784ff2b3100dce31366 +"@mui/core-downloads-tracker@npm:^5.18.0": + version: 5.18.0 + resolution: "@mui/core-downloads-tracker@npm:5.18.0" + checksum: 10c0/d82962a1b69878cf6a6785b6600302a943d474af00bba50169e207ce0bb5d072f1ed65783c7d5ca940cbe4228ab86bc95bb57545cf2cd039d588f2571bbefe0c languageName: node linkType: hard @@ -8589,13 +8712,13 @@ __metadata: languageName: node linkType: hard -"@mui/material@npm:^5.12.2, @mui/material@npm:^5.14.18, @mui/material@npm:^5.16.1": - version: 5.17.1 - resolution: "@mui/material@npm:5.17.1" +"@mui/material@npm:^5.12.2, @mui/material@npm:^5.14.18, @mui/material@npm:^5.16.1, @mui/material@npm:^5.17.1": + version: 5.18.0 + resolution: "@mui/material@npm:5.18.0" dependencies: "@babel/runtime": "npm:^7.23.9" - "@mui/core-downloads-tracker": "npm:^5.17.1" - "@mui/system": "npm:^5.17.1" + "@mui/core-downloads-tracker": "npm:^5.18.0" + "@mui/system": "npm:^5.18.0" "@mui/types": "npm:~7.2.15" "@mui/utils": "npm:^5.17.1" "@popperjs/core": "npm:^2.11.8" @@ -8618,7 +8741,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10c0/55c120879e65833dda16ae1100eb1e2b43d0a7c80a9d1209121c71953c5e7646a1e76d8bd0204382a401af2d8d11b59383e92969525a6cc3f0149a138b25cad7 + checksum: 10c0/a743c9105d08b636d4d9cf0aa276283b9095113771c188c7f5ac6953606dd77a5eb082dbbd889446a9a8573b8676d7249140c29eec55ae8320592e5f02d0f2cb languageName: node linkType: hard @@ -8639,12 +8762,13 @@ __metadata: languageName: node linkType: hard -"@mui/styled-engine@npm:^5.16.14": - version: 5.16.14 - resolution: "@mui/styled-engine@npm:5.16.14" +"@mui/styled-engine@npm:^5.18.0": + version: 5.18.0 + resolution: "@mui/styled-engine@npm:5.18.0" dependencies: "@babel/runtime": "npm:^7.23.9" "@emotion/cache": "npm:^11.13.5" + "@emotion/serialize": "npm:^1.3.3" csstype: "npm:^3.1.3" prop-types: "npm:^15.8.1" peerDependencies: @@ -8656,17 +8780,17 @@ __metadata: optional: true "@emotion/styled": optional: true - checksum: 10c0/cd512faea4ad3ff5a9b315e136a518223ea3e4e34462fe70c56d1f166c46bee0a885ed982773d75c1d56ead62b95989cc5907601e8d65bfa75494b3f3288c2ad + checksum: 10c0/68dad75142eea160fc51abf14915d07afd0e7e7791823f6ea6845b2037fde9de6c17b84247a1f283a1437d130857cb97c1a8474c25c161a934671bc48f205418 languageName: node linkType: hard -"@mui/system@npm:^5.17.1": - version: 5.17.1 - resolution: "@mui/system@npm:5.17.1" +"@mui/system@npm:^5.18.0": + version: 5.18.0 + resolution: "@mui/system@npm:5.18.0" dependencies: "@babel/runtime": "npm:^7.23.9" "@mui/private-theming": "npm:^5.17.1" - "@mui/styled-engine": "npm:^5.16.14" + "@mui/styled-engine": "npm:^5.18.0" "@mui/types": "npm:~7.2.15" "@mui/utils": "npm:^5.17.1" clsx: "npm:^2.1.0" @@ -8684,7 +8808,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10c0/ab74424e536164b720126ddd31ff0ceea4fb51d72f8d18f9be5621b33f8bbdf7fa8c96f8d1d2c4544ddacbaa84df1a197667f10cbe8915e00df103930e40f56e + checksum: 10c0/9f5ad15f08c71560e9723b1f136214a0871079a976285f8b813041081850e1f9e2e9fb00766c15814217852694a521a9a91cde3bed95b8062defa8052f69eabf languageName: node linkType: hard @@ -10347,7 +10471,7 @@ __metadata: languageName: node linkType: hard -"@red-hat-developer-hub/backstage-plugin-theme@npm:^0.14.0": +"@red-hat-developer-hub/backstage-plugin-theme@npm:^0.14.0, @red-hat-developer-hub/backstage-plugin-theme@npm:^0.14.11": version: 0.14.11 resolution: "@red-hat-developer-hub/backstage-plugin-theme@npm:0.14.11" dependencies: @@ -10494,6 +10618,10 @@ __metadata: "@backstage/core-components": "npm:^0.18.13" "@backstage/core-plugin-api": "npm:^1.12.9" "@backstage/dev-utils": "npm:^1.1.26" + "@backstage/frontend-defaults": "npm:^0.5.5" + "@backstage/frontend-plugin-api": "npm:^0.18.0" + "@backstage/frontend-test-utils": "npm:^0.6.3" + "@backstage/plugin-app-react": "npm:^0.2.6" "@backstage/plugin-permission-common": "npm:^0.9.10" "@backstage/plugin-permission-react": "npm:^0.5.4" "@backstage/plugin-search-react": "npm:^1.11.7" @@ -15247,16 +15375,9 @@ __metadata: languageName: node linkType: hard -"app-root-path@npm:^3.1.0": - version: 3.1.0 - resolution: "app-root-path@npm:3.1.0" - checksum: 10c0/4a0fd976de1bffcdb18a5e1f8050091f15d0780e0582bca99aaa9d52de71f0e08e5185355fcffc781180bfb898499e787a2f5ed79b9c448b942b31dc947acaa9 - languageName: node - linkType: hard - -"app@workspace:packages/app": +"app-legacy@workspace:packages/app-legacy": version: 0.0.0-use.local - resolution: "app@workspace:packages/app" + resolution: "app-legacy@workspace:packages/app-legacy" dependencies: "@backstage-community/plugin-rbac": "npm:1.55.2" "@backstage-community/plugin-tech-radar": "npm:^0.7.4" @@ -15310,6 +15431,48 @@ __metadata: languageName: unknown linkType: soft +"app-root-path@npm:^3.1.0": + version: 3.1.0 + resolution: "app-root-path@npm:3.1.0" + checksum: 10c0/4a0fd976de1bffcdb18a5e1f8050091f15d0780e0582bca99aaa9d52de71f0e08e5185355fcffc781180bfb898499e787a2f5ed79b9c448b942b31dc947acaa9 + languageName: node + linkType: hard + +"app@workspace:packages/app": + version: 0.0.0-use.local + resolution: "app@workspace:packages/app" + dependencies: + "@backstage-community/plugin-rbac": "npm:1.55.2" + "@backstage/cli": "npm:^0.36.5" + "@backstage/core-components": "npm:^0.18.13" + "@backstage/core-plugin-api": "npm:^1.12.9" + "@backstage/frontend-defaults": "npm:^0.5.5" + "@backstage/frontend-plugin-api": "npm:^0.18.0" + "@backstage/integration-react": "npm:^1.2.21" + "@backstage/plugin-app-react": "npm:^0.2.6" + "@backstage/plugin-catalog": "npm:^2.0.8" + "@backstage/plugin-user-settings": "npm:^0.9.6" + "@backstage/test-utils": "npm:^1.7.21" + "@backstage/theme": "npm:^0.7.3" + "@backstage/ui": "npm:^0.17.1" + "@mui/icons-material": "npm:^5.17.1" + "@mui/material": "npm:^5.17.1" + "@playwright/test": "npm:1.61.1" + "@red-hat-developer-hub/backstage-plugin-theme": "npm:^0.14.11" + "@red-hat-developer-hub/plugin-cost-management": "workspace:^" + "@testing-library/dom": "npm:^10.0.0" + "@testing-library/jest-dom": "npm:^6.0.0" + "@testing-library/react": "npm:^16.0.0" + "@testing-library/user-event": "npm:^14.0.0" + "@types/react-dom": "npm:*" + cross-env: "npm:^7.0.0" + react: "npm:^18.0.2" + react-dom: "npm:^18.0.2" + react-router: "npm:^6.30.2" + react-router-dom: "npm:^6.30.2" + languageName: unknown + linkType: soft + "append-field@npm:^1.0.0": version: 1.0.0 resolution: "append-field@npm:1.0.0" @@ -20970,6 +21133,28 @@ __metadata: languageName: node linkType: hard +"framer-motion@npm:^12.43.0": + version: 12.43.0 + resolution: "framer-motion@npm:12.43.0" + dependencies: + motion-dom: "npm:^12.43.0" + motion-utils: "npm:^12.39.0" + tslib: "npm:^2.4.0" + peerDependencies: + "@emotion/is-prop-valid": "*" + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/is-prop-valid": + optional: true + react: + optional: true + react-dom: + optional: true + checksum: 10c0/cce3c1daf8f53da4d9299e6bdd2a1e9f08a30e13d84ce1e468cf25285f9ec2a900c812e0aa3dce79e7453a2a3cf328a717451beef9bf0a6175a3d3794116e36a + languageName: node + linkType: hard + "framer-motion@npm:^6.5.1": version: 6.5.1 resolution: "framer-motion@npm:6.5.1" @@ -26569,6 +26754,43 @@ __metadata: languageName: node linkType: hard +"motion-dom@npm:^12.43.0": + version: 12.43.0 + resolution: "motion-dom@npm:12.43.0" + dependencies: + motion-utils: "npm:^12.39.0" + checksum: 10c0/ef7d3f6eaa6cfeb1a939847a8b2581d119f2efc054948921ece0e2b1091f0969c15ca7707b87a46dddac7f156a714878f168e7b9f6ea189eeb7d5d9fbbb99d26 + languageName: node + linkType: hard + +"motion-utils@npm:^12.39.0": + version: 12.39.0 + resolution: "motion-utils@npm:12.39.0" + checksum: 10c0/6d7a2a2cc0797b72410a666a9cc1c201c8e39bf9669670464e433fe1e72af5f0217154c869867b34fbadf3664cf222c0d022bbc4eed7927f201ae971918e7440 + languageName: node + linkType: hard + +"motion@npm:^12.0.0": + version: 12.43.0 + resolution: "motion@npm:12.43.0" + dependencies: + framer-motion: "npm:^12.43.0" + tslib: "npm:^2.4.0" + peerDependencies: + "@emotion/is-prop-valid": "*" + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/is-prop-valid": + optional: true + react: + optional: true + react-dom: + optional: true + checksum: 10c0/15544ecc71d356661a959c78c4f84b3ca91728f5d17265b68912d2bbcf86f20409ebf5acf2f6c82e0c0425d1024267b04abd5b786177a3694bf706f7a3a529d6 + languageName: node + linkType: hard + "mri@npm:1.1.4": version: 1.1.4 resolution: "mri@npm:1.1.4"