From 9331531c0f8121c188da834c27f5d0973ebb62aa Mon Sep 17 00:00:00 2001 From: Preeti Wadhwani Date: Fri, 11 Sep 2026 16:56:45 +0530 Subject: [PATCH 1/9] feat(cost-management): add New Frontend System /alpha export Ship a dual-export frontend so the plugin loads in RHDH 2.1 while keeping legacy PluginRoot wiring. NFS sidebar uses Optimizations and Cost Management. Signed-off-by: Preeti Wadhwani Co-authored-by: Cursor --- .../.changeset/nfs-frontend-alpha.md | 5 + .../cost-management/docs/dynamic-plugin.md | 17 ++ workspaces/cost-management/package.json | 1 + .../plugins/cost-management/README.md | 29 +- .../plugins/cost-management/dev/index.tsx | 46 ++-- .../plugins/cost-management/dev/legacy.tsx | 39 +++ .../plugins/cost-management/package.json | 9 + .../cost-management/report-alpha.api.md | 251 ++++++++++++++++++ .../plugins/cost-management/report.api.md | 2 +- .../plugins/cost-management/src/Router.tsx | 26 +- .../cost-management/src/alpha.test.tsx | 81 ++++++ .../plugins/cost-management/src/alpha.tsx | 163 ++++++++++++ .../src/pages/openshift/OpenShiftPage.tsx | 2 + workspaces/cost-management/yarn.lock | 188 ++++++++++++- 14 files changed, 828 insertions(+), 31 deletions(-) create mode 100644 workspaces/cost-management/.changeset/nfs-frontend-alpha.md create mode 100644 workspaces/cost-management/plugins/cost-management/dev/legacy.tsx create mode 100644 workspaces/cost-management/plugins/cost-management/report-alpha.api.md create mode 100644 workspaces/cost-management/plugins/cost-management/src/alpha.test.tsx create mode 100644 workspaces/cost-management/plugins/cost-management/src/alpha.tsx 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/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..649297125a3 100644 --- a/workspaces/cost-management/package.json +++ b/workspaces/cost-management/package.json @@ -10,6 +10,7 @@ "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/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..a666cf4ca2a 100644 --- a/workspaces/cost-management/plugins/cost-management/src/Router.tsx +++ b/workspaces/cost-management/plugins/cost-management/src/Router.tsx @@ -21,7 +21,26 @@ import { OptimizationsBreakdownPage } from './pages/optimizations-breakdown/Opti 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 (still includes nested /openshift). */ export function Router() { usePatternFlyTheme(); @@ -31,10 +50,7 @@ export function Router() { } /> } /> } /> - } - /> + ); } 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..116a5ec3bce 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: @@ -10494,6 +10617,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" @@ -20970,6 +21097,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 +26718,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" From 12689b017e33ed2ba106120fae4ad2afecd4ba15 Mon Sep 17 00:00:00 2001 From: Preeti Wadhwani Date: Fri, 11 Sep 2026 18:02:53 +0530 Subject: [PATCH 2/9] feat(cost-management): add NFS workspace app shell for local review Split packages/app into an NFS frontend (yarn start) and app-legacy (yarn start:legacy) so reviewers can exercise /alpha against the full RBAC backend with the RHDH theme. Signed-off-by: Preeti Wadhwani Co-authored-by: Cursor --- workspaces/cost-management/AGENTS.md | 9 +- workspaces/cost-management/package.json | 3 +- .../packages/app-legacy/.eslintignore | 1 + .../packages/app-legacy/.eslintrc.js | 1 + .../{app => app-legacy}/e2e-tests/README.md | 0 .../e2e-tests/TEST-COVERAGE.md | 0 .../{app => app-legacy}/e2e-tests/app.test.ts | 0 .../e2e-tests/apply-recommendation.test.ts | 0 .../e2e-tests/dark-theme.test.ts | 0 .../e2e-tests/fixtures/auth.ts | 0 .../fixtures/optimizationResponses.ts | 0 .../e2e-tests/global-setup.ts | 0 .../e2e-tests/live-cluster.test.ts | 0 .../e2e-tests/marketplace.test.ts | 0 .../e2e-tests/navigation.test.ts | 0 .../openshift-cost-management.test.ts | 0 .../e2e-tests/optimization.test.ts | 0 .../pages/ResourceOptimizationPage.ts | 0 .../rbac-dynamic-permissions.test.ts | 0 .../e2e-tests/rbac.test.ts | 0 .../e2e-tests/secure-proxy.test.ts | 0 .../e2e-tests/table-and-pagination.test.ts | 0 .../e2e-tests/utils/apiUtils.ts | 0 .../e2e-tests/utils/devMode.ts | 0 .../e2e-tests/utils/routes.ts | 0 .../packages/app-legacy/package.json | 89 ++++++++++ .../public/android-chrome-192x192.png | Bin 0 -> 13599 bytes .../app-legacy/public/apple-touch-icon.png | Bin 0 -> 12619 bytes .../app-legacy/public/favicon-16x16.png | Bin 0 -> 883 bytes .../app-legacy/public/favicon-32x32.png | Bin 0 -> 1686 bytes .../packages/app-legacy/public/favicon.ico | Bin 0 -> 15086 bytes .../packages/app-legacy/public/index.html | 60 +++++++ .../packages/app-legacy/public/manifest.json | 15 ++ .../packages/app-legacy/public/robots.txt | 2 + .../app-legacy/public/safari-pinned-tab.svg | 1 + .../packages/app-legacy/src/App.test.tsx | 44 +++++ .../packages/app-legacy/src/App.tsx | 157 ++++++++++++++++++ .../packages/{app => app-legacy}/src/apis.ts | 0 .../src/components/Root/LogoFull.tsx | 0 .../src/components/Root/LogoIcon.tsx | 0 .../src/components/Root/Root.tsx | 0 .../src/components/Root/index.ts | 0 .../src/components/catalog/EntityPage.tsx | 0 .../src/components/rhdh-logo/RhdhLogoFull.tsx | 0 .../src/components/rhdh-logo/RhdhLogoIcon.tsx | 0 .../src/components/search/SearchPage.tsx | 0 .../src/hooks/useRhdhTheme.ts | 0 .../packages/app-legacy/src/index.tsx | 22 +++ .../packages/app-legacy/src/setupTests.ts | 17 ++ .../cost-management/packages/app/package.json | 38 +---- .../packages/app/src/App.test.tsx | 2 +- .../cost-management/packages/app/src/App.tsx | 157 ++---------------- .../packages/app/src/index.tsx | 5 +- .../packages/app/src/modules/nav/Sidebar.tsx | 63 +++++++ .../app/src/modules/nav/SidebarLogo.tsx | 55 ++++++ .../packages/app/src/modules/nav/index.ts | 23 +++ .../modules/signIn/SignInPageComponent.tsx | 30 ++++ .../packages/app/src/modules/signIn/index.ts | 30 ++++ workspaces/cost-management/yarn.lock | 144 ++++++++++++++-- 59 files changed, 782 insertions(+), 186 deletions(-) create mode 100644 workspaces/cost-management/packages/app-legacy/.eslintignore create mode 100644 workspaces/cost-management/packages/app-legacy/.eslintrc.js rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/README.md (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/TEST-COVERAGE.md (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/app.test.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/apply-recommendation.test.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/dark-theme.test.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/fixtures/auth.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/fixtures/optimizationResponses.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/global-setup.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/live-cluster.test.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/marketplace.test.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/navigation.test.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/openshift-cost-management.test.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/optimization.test.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/pages/ResourceOptimizationPage.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/rbac-dynamic-permissions.test.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/rbac.test.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/secure-proxy.test.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/table-and-pagination.test.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/utils/apiUtils.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/utils/devMode.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/e2e-tests/utils/routes.ts (100%) create mode 100644 workspaces/cost-management/packages/app-legacy/package.json create mode 100644 workspaces/cost-management/packages/app-legacy/public/android-chrome-192x192.png create mode 100644 workspaces/cost-management/packages/app-legacy/public/apple-touch-icon.png create mode 100644 workspaces/cost-management/packages/app-legacy/public/favicon-16x16.png create mode 100644 workspaces/cost-management/packages/app-legacy/public/favicon-32x32.png create mode 100644 workspaces/cost-management/packages/app-legacy/public/favicon.ico create mode 100644 workspaces/cost-management/packages/app-legacy/public/index.html create mode 100644 workspaces/cost-management/packages/app-legacy/public/manifest.json create mode 100644 workspaces/cost-management/packages/app-legacy/public/robots.txt create mode 100644 workspaces/cost-management/packages/app-legacy/public/safari-pinned-tab.svg create mode 100644 workspaces/cost-management/packages/app-legacy/src/App.test.tsx create mode 100644 workspaces/cost-management/packages/app-legacy/src/App.tsx rename workspaces/cost-management/packages/{app => app-legacy}/src/apis.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/src/components/Root/LogoFull.tsx (100%) rename workspaces/cost-management/packages/{app => app-legacy}/src/components/Root/LogoIcon.tsx (100%) rename workspaces/cost-management/packages/{app => app-legacy}/src/components/Root/Root.tsx (100%) rename workspaces/cost-management/packages/{app => app-legacy}/src/components/Root/index.ts (100%) rename workspaces/cost-management/packages/{app => app-legacy}/src/components/catalog/EntityPage.tsx (100%) rename workspaces/cost-management/packages/{app => app-legacy}/src/components/rhdh-logo/RhdhLogoFull.tsx (100%) rename workspaces/cost-management/packages/{app => app-legacy}/src/components/rhdh-logo/RhdhLogoIcon.tsx (100%) rename workspaces/cost-management/packages/{app => app-legacy}/src/components/search/SearchPage.tsx (100%) rename workspaces/cost-management/packages/{app => app-legacy}/src/hooks/useRhdhTheme.ts (100%) create mode 100644 workspaces/cost-management/packages/app-legacy/src/index.tsx create mode 100644 workspaces/cost-management/packages/app-legacy/src/setupTests.ts create mode 100644 workspaces/cost-management/packages/app/src/modules/nav/Sidebar.tsx create mode 100644 workspaces/cost-management/packages/app/src/modules/nav/SidebarLogo.tsx create mode 100644 workspaces/cost-management/packages/app/src/modules/nav/index.ts create mode 100644 workspaces/cost-management/packages/app/src/modules/signIn/SignInPageComponent.tsx create mode 100644 workspaces/cost-management/packages/app/src/modules/signIn/index.ts 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/package.json b/workspaces/cost-management/package.json index 649297125a3..8a37e756227 100644 --- a/workspaces/cost-management/package.json +++ b/workspaces/cost-management/package.json @@ -6,7 +6,8 @@ "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", 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 0000000000000000000000000000000000000000..eec0ae25b971cae8eb0033c9af7e0f676d1df663 GIT binary patch literal 13599 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 EANz~b2mk;8 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3158830ac778a62ff8f08da0e9eeee6e8ada8bfc GIT binary patch literal 12619 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 VSn|oqbSlsa22WQ%mvv4FO#qym7y1AI literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c0915ece75949f3d917134f55193949927edc633 GIT binary patch literal 1686 zcmd5*YdF&j82@WYD9ojqT$Wo{SSXitp^eRqxy{@++t}P`atlKxQX0)YMm6_KR%p4F z$}J+6LMeH2OXWDHjylI#AJ2#L{k+fbeSgn;`91HK_szmP+3%H7kpcicc}5$z1N!Px-7om}akWbt233fdJ10ExN)z&Z~ATcQ$c2>@ad0I)&=0IMPZ zfJBuET&w|L$8)@+J4!Sl|Nk(stgJlvmnlJ&;dZvl8tO{Q%IJ*5_~q$1+xa={mdc0(T`t!w?RZl>^IxA*%bGu3bF$<*nzTrF> zoQ&9oiO|s=ORAp|1maSVle9kTR(cJ300u{&4iq#dewwjMjZ;umbia|8`fV}#)u6uZ z2{{=Vn6Y8_#3SbXb1ic-&s#nMteerJ5NT@9WJFf_0Z{rXvToRrSOV|5nT6}g)=)7clCVKtHJ1*VPs-V;@XTM)*hF8A>dvO zHk*Y?X8JaA;})mFp7;7TRRuk~8$R*Snh{9txf3X8a4%y!TuP;MHGAGHB;T*cTs()( zPBU^uyOrcpI-AkwlQc~ccBy9xRRz@U7IH^D@piFSO__BF#e(AN-^z2yOmV!NrfGJV z)K=@)T;PiA16X29aqmP@px{GT+&pJ*OCU>i#GE8dw3RuCdu^ zY|d<%y|L(_@zJ9#_m z=Lc$*iilj{bjSDeNH|lRP>yLE)Yq?l!XVsP?uHhR(xnf>5p}vuAuF-j7*tA0oTAH zhFP&ZPMN7z8!K6i|K8u7|AYJvRll0p!ov{KSr|$njU-`o&7fVZN5@WQ|g`Mlirnap=oY~b#iCdHeScp|7sct zd}FL!1NSuuCnVle?ygaP975V!)pzN#*|A{{1* z$fS~)G%HFBO+IIv4>f5HNGY_6SF`0>Gf0kWIFv^nU=X CgrE5U literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5e45e5dfbde6f39603d5be60d933c1af14dffb1e GIT binary patch literal 15086 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 D+pS%} literal 0 HcmV?d00001 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..722b8579245 100644 --- a/workspaces/cost-management/packages/app/package.json +++ b/workspaces/cost-management/packages/app/package.json @@ -19,55 +19,33 @@ "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/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..5a592c2cd6e 100644 --- a/workspaces/cost-management/packages/app/src/App.tsx +++ b/workspaces/cost-management/packages/app/src/App.tsx @@ -14,144 +14,21 @@ * 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 { 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, + 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..3e078e46a52 --- /dev/null +++ b/workspaces/cost-management/packages/app/src/modules/nav/Sidebar.tsx @@ -0,0 +1,63 @@ +/* + * 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 { + 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} /> + )); + + 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/index.ts b/workspaces/cost-management/packages/app/src/modules/nav/index.ts new file mode 100644 index 00000000000..1c6bd8281e8 --- /dev/null +++ b/workspaces/cost-management/packages/app/src/modules/nav/index.ts @@ -0,0 +1,23 @@ +/* + * 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 { SidebarContent } from './Sidebar'; + +export const navModule = createFrontendModule({ + pluginId: 'app', + extensions: [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/yarn.lock b/workspaces/cost-management/yarn.lock index 116a5ec3bce..51fad9007de 100644 --- a/workspaces/cost-management/yarn.lock +++ b/workspaces/cost-management/yarn.lock @@ -8696,6 +8696,13 @@ __metadata: languageName: node linkType: hard +"@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 + "@mui/icons-material@npm:5.18.0, @mui/icons-material@npm:^5.16.1, @mui/icons-material@npm:^5.17.1": version: 5.18.0 resolution: "@mui/icons-material@npm:5.18.0" @@ -8745,6 +8752,39 @@ __metadata: languageName: node linkType: hard +"@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.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" + "@types/react-transition-group": "npm:^4.4.10" + clsx: "npm:^2.1.0" + csstype: "npm:^3.1.3" + prop-types: "npm:^15.8.1" + react-is: "npm:^19.0.0" + react-transition-group: "npm:^4.4.5" + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: 10c0/a743c9105d08b636d4d9cf0aa276283b9095113771c188c7f5ac6953606dd77a5eb082dbbd889446a9a8573b8676d7249140c29eec55ae8320592e5f02d0f2cb + languageName: node + linkType: hard + "@mui/private-theming@npm:^5.17.1": version: 5.17.1 resolution: "@mui/private-theming@npm:5.17.1" @@ -8783,6 +8823,28 @@ __metadata: languageName: node linkType: hard +"@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: + "@emotion/react": ^11.4.1 + "@emotion/styled": ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + checksum: 10c0/68dad75142eea160fc51abf14915d07afd0e7e7791823f6ea6845b2037fde9de6c17b84247a1f283a1437d130857cb97c1a8474c25c161a934671bc48f205418 + languageName: node + linkType: hard + "@mui/system@npm:^5.17.1": version: 5.17.1 resolution: "@mui/system@npm:5.17.1" @@ -8811,6 +8873,34 @@ __metadata: languageName: node linkType: hard +"@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.18.0" + "@mui/types": "npm:~7.2.15" + "@mui/utils": "npm:^5.17.1" + clsx: "npm:^2.1.0" + csstype: "npm:^3.1.3" + prop-types: "npm:^15.8.1" + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: 10c0/9f5ad15f08c71560e9723b1f136214a0871079a976285f8b813041081850e1f9e2e9fb00766c15814217852694a521a9a91cde3bed95b8062defa8052f69eabf + languageName: node + linkType: hard + "@mui/types@npm:~7.2.15": version: 7.2.24 resolution: "@mui/types@npm:7.2.24" @@ -10470,7 +10560,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: @@ -15374,16 +15464,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" @@ -15437,6 +15520,47 @@ __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/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" From 3604ab806ee01cd5a670c5187f788b80de221997 Mon Sep 17 00:00:00 2001 From: Preeti Wadhwani Date: Fri, 11 Sep 2026 18:10:28 +0530 Subject: [PATCH 3/9] chore(cost-management): dedupe yarn.lock after NFS app MUI deps Co-authored-by: Cursor --- workspaces/cost-management/yarn.lock | 91 +--------------------------- 1 file changed, 1 insertion(+), 90 deletions(-) diff --git a/workspaces/cost-management/yarn.lock b/workspaces/cost-management/yarn.lock index 51fad9007de..9984034865c 100644 --- a/workspaces/cost-management/yarn.lock +++ b/workspaces/cost-management/yarn.lock @@ -8689,13 +8689,6 @@ __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 - languageName: node - linkType: hard - "@mui/core-downloads-tracker@npm:^5.18.0": version: 5.18.0 resolution: "@mui/core-downloads-tracker@npm:5.18.0" @@ -8719,40 +8712,7 @@ __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" - dependencies: - "@babel/runtime": "npm:^7.23.9" - "@mui/core-downloads-tracker": "npm:^5.17.1" - "@mui/system": "npm:^5.17.1" - "@mui/types": "npm:~7.2.15" - "@mui/utils": "npm:^5.17.1" - "@popperjs/core": "npm:^2.11.8" - "@types/react-transition-group": "npm:^4.4.10" - clsx: "npm:^2.1.0" - csstype: "npm:^3.1.3" - prop-types: "npm:^15.8.1" - react-is: "npm:^19.0.0" - react-transition-group: "npm:^4.4.5" - peerDependencies: - "@emotion/react": ^11.5.0 - "@emotion/styled": ^11.3.0 - "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@emotion/react": - optional: true - "@emotion/styled": - optional: true - "@types/react": - optional: true - checksum: 10c0/55c120879e65833dda16ae1100eb1e2b43d0a7c80a9d1209121c71953c5e7646a1e76d8bd0204382a401af2d8d11b59383e92969525a6cc3f0149a138b25cad7 - languageName: node - linkType: hard - -"@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: @@ -8802,27 +8762,6 @@ __metadata: languageName: node linkType: hard -"@mui/styled-engine@npm:^5.16.14": - version: 5.16.14 - resolution: "@mui/styled-engine@npm:5.16.14" - dependencies: - "@babel/runtime": "npm:^7.23.9" - "@emotion/cache": "npm:^11.13.5" - csstype: "npm:^3.1.3" - prop-types: "npm:^15.8.1" - peerDependencies: - "@emotion/react": ^11.4.1 - "@emotion/styled": ^11.3.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@emotion/react": - optional: true - "@emotion/styled": - optional: true - checksum: 10c0/cd512faea4ad3ff5a9b315e136a518223ea3e4e34462fe70c56d1f166c46bee0a885ed982773d75c1d56ead62b95989cc5907601e8d65bfa75494b3f3288c2ad - languageName: node - linkType: hard - "@mui/styled-engine@npm:^5.18.0": version: 5.18.0 resolution: "@mui/styled-engine@npm:5.18.0" @@ -8845,34 +8784,6 @@ __metadata: languageName: node linkType: hard -"@mui/system@npm:^5.17.1": - version: 5.17.1 - resolution: "@mui/system@npm:5.17.1" - dependencies: - "@babel/runtime": "npm:^7.23.9" - "@mui/private-theming": "npm:^5.17.1" - "@mui/styled-engine": "npm:^5.16.14" - "@mui/types": "npm:~7.2.15" - "@mui/utils": "npm:^5.17.1" - clsx: "npm:^2.1.0" - csstype: "npm:^3.1.3" - prop-types: "npm:^15.8.1" - peerDependencies: - "@emotion/react": ^11.5.0 - "@emotion/styled": ^11.3.0 - "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@emotion/react": - optional: true - "@emotion/styled": - optional: true - "@types/react": - optional: true - checksum: 10c0/ab74424e536164b720126ddd31ff0ceea4fb51d72f8d18f9be5621b33f8bbdf7fa8c96f8d1d2c4544ddacbaa84df1a197667f10cbe8915e00df103930e40f56e - languageName: node - linkType: hard - "@mui/system@npm:^5.18.0": version: 5.18.0 resolution: "@mui/system@npm:5.18.0" From 3a7026cd938319597daa829de187a8bbd4736882 Mon Sep 17 00:00:00 2001 From: Preeti Wadhwani Date: Fri, 11 Sep 2026 18:33:46 +0530 Subject: [PATCH 4/9] fix(cost-management): redirect NFS app home and add RBAC nav Yarn start landed on `/` with no page. Redirect to `/catalog` like the legacy app, and register the RBAC `/alpha` plugin so reviewers can edit policies without switching to app-legacy. Signed-off-by: Preeti Wadhwani Assisted-by: Cursor Grok 4.6 Co-authored-by: Cursor --- .../cost-management/packages/app/package.json | 1 + .../cost-management/packages/app/src/App.tsx | 5 ++++ .../packages/app/src/modules/nav/Sidebar.tsx | 5 ++++ .../app/src/modules/nav/homeRedirect.tsx | 30 +++++++++++++++++++ .../packages/app/src/modules/nav/index.ts | 3 +- workspaces/cost-management/yarn.lock | 1 + 6 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 workspaces/cost-management/packages/app/src/modules/nav/homeRedirect.tsx diff --git a/workspaces/cost-management/packages/app/package.json b/workspaces/cost-management/packages/app/package.json index 722b8579245..aaa9d1f32e1 100644 --- a/workspaces/cost-management/packages/app/package.json +++ b/workspaces/cost-management/packages/app/package.json @@ -19,6 +19,7 @@ "lint": "backstage-cli package lint" }, "dependencies": { + "@backstage-community/plugin-rbac": "1.55.2", "@backstage/cli": "^0.36.5", "@backstage/core-components": "^0.18.13", "@backstage/core-plugin-api": "^1.12.9", diff --git a/workspaces/cost-management/packages/app/src/App.tsx b/workspaces/cost-management/packages/app/src/App.tsx index 5a592c2cd6e..c14d8f010f9 100644 --- a/workspaces/cost-management/packages/app/src/App.tsx +++ b/workspaces/cost-management/packages/app/src/App.tsx @@ -14,6 +14,9 @@ * limitations under the License. */ +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'; @@ -29,6 +32,8 @@ export default createApp({ catalogPlugin, userSettingsPlugin, costManagementPlugin, + rbacPlugin, + rbacTranslationsModule, signInModule, ], }); diff --git a/workspaces/cost-management/packages/app/src/modules/nav/Sidebar.tsx b/workspaces/cost-management/packages/app/src/modules/nav/Sidebar.tsx index 3e078e46a52..7a7b50ee6a0 100644 --- a/workspaces/cost-management/packages/app/src/modules/nav/Sidebar.tsx +++ b/workspaces/cost-management/packages/app/src/modules/nav/Sidebar.tsx @@ -35,6 +35,10 @@ export const SidebarContent = NavContentBlueprint.make({ 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'); + const rbacItem = nav.take('page:rbac'); + return ( @@ -49,6 +53,7 @@ export const SidebarContent = NavContentBlueprint.make({ + {rbacItem} } 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 index 1c6bd8281e8..a3e1f522c22 100644 --- a/workspaces/cost-management/packages/app/src/modules/nav/index.ts +++ b/workspaces/cost-management/packages/app/src/modules/nav/index.ts @@ -15,9 +15,10 @@ */ import { createFrontendModule } from '@backstage/frontend-plugin-api'; +import { homeRedirectPage } from './homeRedirect'; import { SidebarContent } from './Sidebar'; export const navModule = createFrontendModule({ pluginId: 'app', - extensions: [SidebarContent], + extensions: [homeRedirectPage, SidebarContent], }); diff --git a/workspaces/cost-management/yarn.lock b/workspaces/cost-management/yarn.lock index 9984034865c..e52d02f9fa9 100644 --- a/workspaces/cost-management/yarn.lock +++ b/workspaces/cost-management/yarn.lock @@ -15442,6 +15442,7 @@ __metadata: 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" From 1b1f122bb026d4399170d6292bb605429180b6e3 Mon Sep 17 00:00:00 2001 From: Preeti Wadhwani Date: Fri, 11 Sep 2026 19:01:45 +0530 Subject: [PATCH 5/9] fix(cost-management): hide NFS RBAC nav for unauthorized users Reuse the legacy Administration sidebar item so RBAC is shown only when getUserAuthorization() returns Authorized, matching yarn start:legacy. Signed-off-by: Preeti Wadhwani Assisted-by: Cursor Grok 4.6 Co-authored-by: Cursor --- .../packages/app/src/modules/nav/Sidebar.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/workspaces/cost-management/packages/app/src/modules/nav/Sidebar.tsx b/workspaces/cost-management/packages/app/src/modules/nav/Sidebar.tsx index 7a7b50ee6a0..9702191ba57 100644 --- a/workspaces/cost-management/packages/app/src/modules/nav/Sidebar.tsx +++ b/workspaces/cost-management/packages/app/src/modules/nav/Sidebar.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { Administration } from '@backstage-community/plugin-rbac'; import { Sidebar, SidebarDivider, @@ -37,7 +38,9 @@ export const SidebarContent = NavContentBlueprint.make({ // Consume so the `/` → `/catalog` redirect is not listed in the sidebar. nav.take('page:app/home-redirect'); - const rbacItem = nav.take('page:rbac'); + // Consume page:rbac so it is not always shown. Administration hides + // itself unless getUserAuthorization() returns Authorized (legacy match). + nav.take('page:rbac'); return ( @@ -53,7 +56,7 @@ export const SidebarContent = NavContentBlueprint.make({ - {rbacItem} + } From 939bd72888af2acf8f3977211e0065f5700b56ac Mon Sep 17 00:00:00 2001 From: Preeti Wadhwani Date: Tue, 15 Sep 2026 13:43:42 +0530 Subject: [PATCH 6/9] fix(cost-management): drop unused nested /optimizations routes The list page is already path "/". NFS mounts OptimizationsRouter at /cost-management/optimizations, so the extra child was unreachable. Signed-off-by: Preeti Wadhwani Assisted-by: Cursor Grok 4.6 Co-authored-by: Cursor --- .../cost-management/plugins/cost-management/src/Router.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/workspaces/cost-management/plugins/cost-management/src/Router.tsx b/workspaces/cost-management/plugins/cost-management/src/Router.tsx index a666cf4ca2a..d8f8c86f997 100644 --- a/workspaces/cost-management/plugins/cost-management/src/Router.tsx +++ b/workspaces/cost-management/plugins/cost-management/src/Router.tsx @@ -33,7 +33,6 @@ export function OptimizationsRouter() { return ( } /> - } /> } /> @@ -47,7 +46,6 @@ export function Router() { return ( } /> - } /> } /> } /> From 4741b892646b80114cefdbd2f0a828c1a2a17df6 Mon Sep 17 00:00:00 2001 From: Preeti Wadhwani Date: Tue, 15 Sep 2026 15:58:17 +0530 Subject: [PATCH 7/9] fix(cost-management): drop unused nested OpenShift route OpenShift is only reached at /cost-management/openshift via the standalone page. The child under Optimizations was unused and made usePatternFlyTheme run twice on that path. Signed-off-by: Preeti Wadhwani Assisted-by: Cursor Grok 4.6 Co-authored-by: Cursor --- .../cost-management/plugins/cost-management/src/Router.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/workspaces/cost-management/plugins/cost-management/src/Router.tsx b/workspaces/cost-management/plugins/cost-management/src/Router.tsx index d8f8c86f997..a94b2dcc955 100644 --- a/workspaces/cost-management/plugins/cost-management/src/Router.tsx +++ b/workspaces/cost-management/plugins/cost-management/src/Router.tsx @@ -18,7 +18,6 @@ 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'; const notFound = ; @@ -39,14 +38,13 @@ export function OptimizationsRouter() { ); } -/** @public Legacy ResourceOptimizationPage mount (still includes nested /openshift). */ +/** @public Legacy ResourceOptimizationPage mount (list + breakdown). */ export function Router() { usePatternFlyTheme(); return ( } /> - } /> } /> From 9bc13d90fd79814df72f0e6ab6583bd1025fcfef Mon Sep 17 00:00:00 2001 From: Preeti Wadhwani Date: Tue, 15 Sep 2026 16:26:05 +0530 Subject: [PATCH 8/9] fix(cost-management): point Playwright config at app-legacy e2e-tests Co-authored-by: Cursor --- workspaces/cost-management/playwright.config.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/workspaces/cost-management/playwright.config.ts b/workspaces/cost-management/playwright.config.ts index 9b36e7ca6a8..d99797c617c 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', + ), } : {}), @@ -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, From c07b7c392b4948d6aeaf82d80e7ba98fca4a09ac Mon Sep 17 00:00:00 2001 From: Preeti Wadhwani Date: Tue, 15 Sep 2026 16:38:41 +0530 Subject: [PATCH 9/9] fix(cost-management): start legacy app for Playwright e2e tests Co-authored-by: Cursor --- workspaces/cost-management/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/cost-management/playwright.config.ts b/workspaces/cost-management/playwright.config.ts index d99797c617c..90edf37f129 100644 --- a/workspaces/cost-management/playwright.config.ts +++ b/workspaces/cost-management/playwright.config.ts @@ -40,7 +40,7 @@ export default defineConfig({ ? [] : [ { - command: 'yarn start-app', + command: 'yarn workspace app-legacy start', port: 3000, reuseExistingServer: true, timeout: 120_000,