From ec0b37ec135401ed215a14d16e646d3cc81ddd97 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 6 Aug 2026 10:08:36 -0400 Subject: [PATCH 01/48] chore: initial NFS migration Assisted-By: Cursor Signed-off-by: Frank Kong rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- package.json | 3 +- packages/app-next/config.d.ts | 49 ++++ packages/app-next/package.json | 17 +- packages/app-next/src/App.tsx | 31 ++- .../src/apis/LearningPathApiClient.ts | 72 ++++++ packages/app-next/src/apis/apisModule.ts | 98 ++++++++ .../src/hooks/useThemedConfig.test.tsx | 93 ++++++++ .../app-next/src/hooks/useThemedConfig.ts | 39 ++++ packages/app-next/src/hooks/useTranslation.ts | 10 + .../app-next/src/modules/nav/LogoFull.tsx | 80 +++++++ .../app-next/src/modules/nav/LogoIcon.tsx | 19 ++ packages/app-next/src/modules/nav/Sidebar.tsx | 70 ++++++ .../src/modules/nav/SidebarLogo.test.tsx | 122 ++++++++++ .../app-next/src/modules/nav/SidebarLogo.tsx | 73 ++++++ packages/app-next/src/modules/nav/index.ts | 16 ++ .../catalog-import/catalog-import-en.ts | 13 ++ .../catalog-import/catalog-import.ts | 14 ++ .../src/translations/catalog-import/de.ts | 29 +++ .../src/translations/catalog-import/es.ts | 29 +++ .../src/translations/catalog-import/fr.ts | 130 +++++++++++ .../src/translations/catalog-import/it.ts | 27 +++ .../src/translations/catalog-import/ja.ts | 27 +++ .../src/translations/catalog/catalog-en.ts | 12 + .../src/translations/catalog/catalog.ts | 14 ++ .../app-next/src/translations/catalog/de.ts | 26 +++ .../app-next/src/translations/catalog/es.ts | 26 +++ .../app-next/src/translations/catalog/fr.ts | 120 ++++++++++ .../app-next/src/translations/catalog/it.ts | 26 +++ .../app-next/src/translations/catalog/ja.ts | 26 +++ .../core-components/core-components-en.ts | 55 +++++ .../core-components/core-components.ts | 14 ++ .../src/translations/core-components/de.ts | 42 ++++ .../src/translations/core-components/es.ts | 41 ++++ .../src/translations/core-components/fr.ts | 87 ++++++++ .../src/translations/core-components/it.ts | 39 ++++ .../src/translations/core-components/ja.ts | 39 ++++ packages/app-next/src/translations/rhdh/de.ts | 113 ++++++++++ packages/app-next/src/translations/rhdh/es.ts | 114 ++++++++++ packages/app-next/src/translations/rhdh/fr.ts | 114 ++++++++++ .../app-next/src/translations/rhdh/index.ts | 35 +++ packages/app-next/src/translations/rhdh/it.ts | 112 ++++++++++ packages/app-next/src/translations/rhdh/ja.ts | 114 ++++++++++ .../app-next/src/translations/rhdh/ref.ts | 211 ++++++++++++++++++ .../src/translations/scaffolder/de.ts | 33 +++ .../src/translations/scaffolder/es.ts | 33 +++ .../src/translations/scaffolder/fr.ts | 31 +++ .../src/translations/scaffolder/it.ts | 31 +++ .../src/translations/scaffolder/ja.ts | 31 +++ .../translations/scaffolder/scaffolder-en.ts | 17 ++ .../src/translations/scaffolder/scaffolder.ts | 14 ++ .../app-next/src/translations/search/ja.ts | 26 +++ .../src/translations/search/search-de.ts | 28 +++ .../src/translations/search/search-en.ts | 12 + .../src/translations/search/search-es.ts | 28 +++ .../src/translations/search/search-fr.ts | 32 +++ .../src/translations/search/search-it.ts | 26 +++ .../src/translations/search/search.ts | 14 ++ .../src/translations/translationsModule.ts | 50 +++++ .../src/translations/user-settings/ja.ts | 26 +++ .../user-settings/user-settings-de.ts | 28 +++ .../user-settings/user-settings-en.ts | 12 + .../user-settings/user-settings-es.ts | 28 +++ .../user-settings/user-settings-fr.ts | 84 +++++++ .../user-settings/user-settings-it.ts | 26 +++ .../user-settings/user-settings.ts | 14 ++ packages/app-next/tsconfig.json | 10 + yarn.lock | 56 ++++- 67 files changed, 3120 insertions(+), 11 deletions(-) create mode 100644 packages/app-next/config.d.ts create mode 100644 packages/app-next/src/apis/LearningPathApiClient.ts create mode 100644 packages/app-next/src/apis/apisModule.ts create mode 100644 packages/app-next/src/hooks/useThemedConfig.test.tsx create mode 100644 packages/app-next/src/hooks/useThemedConfig.ts create mode 100644 packages/app-next/src/hooks/useTranslation.ts create mode 100644 packages/app-next/src/modules/nav/LogoFull.tsx create mode 100644 packages/app-next/src/modules/nav/LogoIcon.tsx create mode 100644 packages/app-next/src/modules/nav/Sidebar.tsx create mode 100644 packages/app-next/src/modules/nav/SidebarLogo.test.tsx create mode 100644 packages/app-next/src/modules/nav/SidebarLogo.tsx create mode 100644 packages/app-next/src/modules/nav/index.ts create mode 100644 packages/app-next/src/translations/catalog-import/catalog-import-en.ts create mode 100644 packages/app-next/src/translations/catalog-import/catalog-import.ts create mode 100644 packages/app-next/src/translations/catalog-import/de.ts create mode 100644 packages/app-next/src/translations/catalog-import/es.ts create mode 100644 packages/app-next/src/translations/catalog-import/fr.ts create mode 100644 packages/app-next/src/translations/catalog-import/it.ts create mode 100644 packages/app-next/src/translations/catalog-import/ja.ts create mode 100644 packages/app-next/src/translations/catalog/catalog-en.ts create mode 100644 packages/app-next/src/translations/catalog/catalog.ts create mode 100644 packages/app-next/src/translations/catalog/de.ts create mode 100644 packages/app-next/src/translations/catalog/es.ts create mode 100644 packages/app-next/src/translations/catalog/fr.ts create mode 100644 packages/app-next/src/translations/catalog/it.ts create mode 100644 packages/app-next/src/translations/catalog/ja.ts create mode 100644 packages/app-next/src/translations/core-components/core-components-en.ts create mode 100644 packages/app-next/src/translations/core-components/core-components.ts create mode 100644 packages/app-next/src/translations/core-components/de.ts create mode 100644 packages/app-next/src/translations/core-components/es.ts create mode 100644 packages/app-next/src/translations/core-components/fr.ts create mode 100644 packages/app-next/src/translations/core-components/it.ts create mode 100644 packages/app-next/src/translations/core-components/ja.ts create mode 100644 packages/app-next/src/translations/rhdh/de.ts create mode 100644 packages/app-next/src/translations/rhdh/es.ts create mode 100644 packages/app-next/src/translations/rhdh/fr.ts create mode 100644 packages/app-next/src/translations/rhdh/index.ts create mode 100644 packages/app-next/src/translations/rhdh/it.ts create mode 100644 packages/app-next/src/translations/rhdh/ja.ts create mode 100644 packages/app-next/src/translations/rhdh/ref.ts create mode 100644 packages/app-next/src/translations/scaffolder/de.ts create mode 100644 packages/app-next/src/translations/scaffolder/es.ts create mode 100644 packages/app-next/src/translations/scaffolder/fr.ts create mode 100644 packages/app-next/src/translations/scaffolder/it.ts create mode 100644 packages/app-next/src/translations/scaffolder/ja.ts create mode 100644 packages/app-next/src/translations/scaffolder/scaffolder-en.ts create mode 100644 packages/app-next/src/translations/scaffolder/scaffolder.ts create mode 100644 packages/app-next/src/translations/search/ja.ts create mode 100644 packages/app-next/src/translations/search/search-de.ts create mode 100644 packages/app-next/src/translations/search/search-en.ts create mode 100644 packages/app-next/src/translations/search/search-es.ts create mode 100644 packages/app-next/src/translations/search/search-fr.ts create mode 100644 packages/app-next/src/translations/search/search-it.ts create mode 100644 packages/app-next/src/translations/search/search.ts create mode 100644 packages/app-next/src/translations/translationsModule.ts create mode 100644 packages/app-next/src/translations/user-settings/ja.ts create mode 100644 packages/app-next/src/translations/user-settings/user-settings-de.ts create mode 100644 packages/app-next/src/translations/user-settings/user-settings-en.ts create mode 100644 packages/app-next/src/translations/user-settings/user-settings-es.ts create mode 100644 packages/app-next/src/translations/user-settings/user-settings-fr.ts create mode 100644 packages/app-next/src/translations/user-settings/user-settings-it.ts create mode 100644 packages/app-next/src/translations/user-settings/user-settings.ts create mode 100644 packages/app-next/tsconfig.json diff --git a/package.json b/package.json index 964f4fdcdd..163aac57c1 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "scripts": { "prepare": "husky install", "start": "NODE_OPTIONS=--no-node-snapshot turbo run start --filter=backend", - "dev": "NODE_OPTIONS=--no-node-snapshot turbo run start --filter=backend --filter=app", + "dev:legacy": "NODE_OPTIONS=--no-node-snapshot turbo run start --filter=backend --filter=app", + "dev": "NODE_OPTIONS=--no-node-snapshot turbo run start --filter=backend --filter=app-next", "build": "turbo run build", "build:dockerfile": "bash ./scripts/update-Dockerfile.sh", "tsc": "turbo run tsc", diff --git a/packages/app-next/config.d.ts b/packages/app-next/config.d.ts new file mode 100644 index 0000000000..7bdb084e80 --- /dev/null +++ b/packages/app-next/config.d.ts @@ -0,0 +1,49 @@ +export interface Config { + app: { + branding?: { + /** + * Base64 URI for the full logo. If the value is a string, it is used as the logo for both themes. + * @visibility frontend + */ + // this config is copied to rhdh-plugins/global-header config.d.ts and should be kept in sync + fullLogo?: + | string + | { + /** + * Base64 URI for the logo in light theme + * @visibility frontend + */ + light: string; + /** + * Base64 URI for the logo in dark theme + * @visibility frontend + */ + dark: string; + }; + /** + * size Configuration for the full logo + * The following units are supported: , px, em, rem, + * @visibility frontend + */ + fullLogoWidth?: string | number; + /** + * Base64 URI for the icon logo. If the value is a string, it is used as the logo for both themes. + * @visibility frontend + */ + iconLogo?: + | string + | { + /** + * Base64 URI for the icon logo in light theme + * @visibility frontend + */ + light: string; + /** + * Base64 URI for the icon logo in dark theme + * @visibility frontend + */ + dark: string; + }; + }; + }; +} diff --git a/packages/app-next/package.json b/packages/app-next/package.json index 9e88410580..9679c621bf 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -21,8 +21,9 @@ "lint": "backstage-cli package lint", "lint:check": "backstage-cli package lint", "lint:fix": "backstage-cli package lint --fix", - "start": "backstage-cli package start --config ../../app-config.yaml", - "test": "backstage-cli package test --passWithNoTests --coverage" + "start": "backstage-cli package start", + "test": "backstage-cli package test --passWithNoTests --coverage", + "tsc": "tsc" }, "browserslist": { "production": [ @@ -52,6 +53,7 @@ "@backstage/frontend-plugin-api": "0.17.2", "@backstage/integration-react": "1.2.19", "@backstage/plugin-app": "0.5.0", + "@backstage/plugin-app-react": "0.2.4", "@backstage/plugin-app-visualizer": "0.2.5", "@backstage/plugin-auth-react": "0.1.28", "@backstage/plugin-catalog": "2.0.6", @@ -72,16 +74,24 @@ "@material-ui/core": "4.12.4", "@material-ui/icons": "4.11.3", "@material-ui/lab": "4.0.0-alpha.61", + "@mui/icons-material": "5.18.0", + "@mui/material": "5.18.0", "@octokit/rest": "19.0.13", + "@red-hat-developer-hub/backstage-plugin-app-auth": "0.1.0", + "@red-hat-developer-hub/backstage-plugin-app-integrations": "0.1.0", + "@red-hat-developer-hub/backstage-plugin-app-react": "0.1.0", + "@red-hat-developer-hub/backstage-plugin-theme": "0.14.7", "history": "5.3.0", "react": "18.3.1", "react-dom": "18.3.1", "react-router": "6.30.4", "react-router-dom": "6.30.4", "react-use": "17.6.1", + "tss-react": "4.9.21", "zen-observable": "0.10.0" }, "devDependencies": { + "@backstage/frontend-test-utils": "0.6.1", "@backstage/test-utils": "1.7.19", "@testing-library/dom": "9.3.4", "@testing-library/jest-dom": "6.9.1", @@ -91,6 +101,7 @@ "@types/react": "18.3.31", "@types/react-dom": "18.3.7", "@types/zen-observable": "0.8.7", - "cross-env": "7.0.3" + "cross-env": "7.0.3", + "typescript": "5.9.3" } } diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index fafcd6a17b..d835c5e14f 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -1,19 +1,38 @@ import { createApp } from '@backstage/frontend-defaults'; +import { dynamicFrontendFeaturesLoader } from '@backstage/frontend-dynamic-feature-loader'; import appVisualizerPlugin from '@backstage/plugin-app-visualizer'; import catalogPlugin from '@backstage/plugin-catalog/alpha'; import scaffolderPlugin from '@backstage/plugin-scaffolder/alpha'; import searchPlugin from '@backstage/plugin-search/alpha'; import userSettingsPlugin from '@backstage/plugin-user-settings/alpha'; -import { dynamicFrontendFeaturesLoader } from '@backstage/frontend-dynamic-feature-loader'; + +import { appAuthModule } from '@red-hat-developer-hub/backstage-plugin-app-auth/alpha'; +import { appIntegrationsModule } from '@red-hat-developer-hub/backstage-plugin-app-integrations/alpha'; +import { appDrawerModule } from '@red-hat-developer-hub/backstage-plugin-app-react/alpha'; +import { rhdhThemeModule } from '@red-hat-developer-hub/backstage-plugin-theme/alpha'; + +import { rhdhApisModule } from './apis/apisModule'; +import { navModule } from './modules/nav'; +import { rhdhTranslationsModule } from './translations/translationsModule'; const app = createApp({ features: [ - appVisualizerPlugin, - catalogPlugin, - scaffolderPlugin, - searchPlugin, + // Upstream Backstage plugins + appVisualizerPlugin, + catalogPlugin, + scaffolderPlugin, + searchPlugin, userSettingsPlugin, - dynamicFrontendFeaturesLoader() + dynamicFrontendFeaturesLoader(), + // RHDH modules (from rhdh-plugins workspace) + appAuthModule, // config-driven sign-in page + OIDC/Keycloak/PingFederate/Auth0/SAML auth APIs + appIntegrationsModule, // SCM integrations + SCM auth APIs + appDrawerModule, // drawer infrastructure (lightspeed, quickstarts, etc.) + // RHDH modules (local to app-next) + navModule, // RHDH-branded sidebar (logo, menu ordering, drawer toggle) + rhdhApisModule, // storage, learning-path, catalog-graph APIs + rhdhTranslationsModule, // RHDH + plugin translation overrides (de, es, fr, it, ja) + rhdhThemeModule, // RHDH light/dark themes ], }); diff --git a/packages/app-next/src/apis/LearningPathApiClient.ts b/packages/app-next/src/apis/LearningPathApiClient.ts new file mode 100644 index 0000000000..49a0acfdd4 --- /dev/null +++ b/packages/app-next/src/apis/LearningPathApiClient.ts @@ -0,0 +1,72 @@ +import { + ConfigApi, + createApiRef, + DiscoveryApi, + IdentityApi, +} from '@backstage/core-plugin-api'; + +const DEFAULT_PROXY_PATH = '/developer-hub'; + +export type LearningPathLink = { + label: string; + url: string; + description?: string; + hours?: number; + minutes?: number; + paths?: number; +}; + +export interface LearningPathApi { + getLearningPathData(): Promise; +} + +export const learningPathApiRef = createApiRef({ + id: 'app.developer-hub.learning-path.service', +}); + +export type Options = { + discoveryApi: DiscoveryApi; + configApi: ConfigApi; + identityApi: IdentityApi; +}; + +export class LearningPathApiClient implements LearningPathApi { + private readonly discoveryApi: DiscoveryApi; + private readonly configApi: ConfigApi; + private readonly identityApi: IdentityApi; + + constructor(options: Options) { + this.discoveryApi = options.discoveryApi; + this.configApi = options.configApi; + this.identityApi = options.identityApi; + } + + private async getBaseUrl() { + const proxyPath = + this.configApi.getOptionalString('developerHub.proxyPath') ?? + DEFAULT_PROXY_PATH; + return `${await this.discoveryApi.getBaseUrl('proxy')}${proxyPath}`; + } + + private async fetcher(url: string) { + const { token: idToken } = await this.identityApi.getCredentials(); + const response = await fetch(url, { + headers: { + 'Content-Type': 'application/json', + ...(idToken && { Authorization: `Bearer ${idToken}` }), + }, + }); + if (!response.ok) { + throw new Error( + `failed to fetch data, status ${response.status}: ${response.statusText}`, + ); + } + return await response.json(); + } + + async getLearningPathData() { + const proxyUrl = await this.getBaseUrl(); + const data = await this.fetcher(`${proxyUrl}/learning-paths`); + return data; + } +} diff --git a/packages/app-next/src/apis/apisModule.ts b/packages/app-next/src/apis/apisModule.ts new file mode 100644 index 0000000000..0ba57b18fb --- /dev/null +++ b/packages/app-next/src/apis/apisModule.ts @@ -0,0 +1,98 @@ +import { WebStorage } from '@backstage/core-app-api'; +import { + configApiRef, + discoveryApiRef, + errorApiRef, + fetchApiRef, + identityApiRef, + storageApiRef, +} from '@backstage/core-plugin-api'; +import { ApiBlueprint, createFrontendModule } from '@backstage/frontend-plugin-api'; +import { + ALL_RELATION_PAIRS, + ALL_RELATIONS, + catalogGraphApiRef, + DefaultCatalogGraphApi, +} from '@backstage/plugin-catalog-graph'; +import { UserSettingsStorage } from '@backstage/plugin-user-settings'; + +import { + LearningPathApiClient, + learningPathApiRef, +} from './LearningPathApiClient'; + +// Custom relations from @backstage-community/plugin-catalog-backend-module-scaffolder-relation-processor +const RELATION_SCAFFOLDED_FROM = 'scaffoldedFrom'; +const RELATION_SCAFFOLDER_OF = 'scaffolderOf'; + +const storageApi = ApiBlueprint.make({ + name: 'storage', + params: defineParams => + defineParams({ + api: storageApiRef, + deps: { + discoveryApi: discoveryApiRef, + errorApi: errorApiRef, + fetchApi: fetchApiRef, + identityApi: identityApiRef, + configApi: configApiRef, + }, + factory: deps => { + const persistence = + deps.configApi.getOptionalString('userSettings.persistence') ?? + 'database'; + return persistence === 'browser' + ? WebStorage.create(deps) + : UserSettingsStorage.create(deps); + }, + }), +}); + +const learningPathApi = ApiBlueprint.make({ + name: 'learning-path', + params: defineParams => + defineParams({ + api: learningPathApiRef, + deps: { + discoveryApi: discoveryApiRef, + configApi: configApiRef, + identityApi: identityApiRef, + }, + factory: ({ discoveryApi, configApi, identityApi }) => + new LearningPathApiClient({ discoveryApi, configApi, identityApi }), + }), +}); + +// Catalog Graph API with custom scaffolder relations for @backstage-community/plugin-catalog-backend-module-scaffolder-relation-processor +const catalogGraphApi = ApiBlueprint.make({ + name: 'catalog-graph', + params: defineParams => + defineParams({ + api: catalogGraphApiRef, + deps: {}, + factory: () => + new DefaultCatalogGraphApi({ + knownRelations: [ + ...ALL_RELATIONS, + RELATION_SCAFFOLDED_FROM, + RELATION_SCAFFOLDER_OF, + ], + knownRelationPairs: [ + ...ALL_RELATION_PAIRS, + [RELATION_SCAFFOLDER_OF, RELATION_SCAFFOLDED_FROM], + ], + defaultRelationTypes: { exclude: [] }, + }), + }), +}); + +/** + * RHDH storage, learning-path, and catalog-graph APIs for `pluginId: 'app'`. + * Auth APIs and SCM integrations are provided by `appAuthModule` / + * `appIntegrationsModule` from `@red-hat-developer-hub/backstage-plugin-app-auth` + * and `@red-hat-developer-hub/backstage-plugin-app-integrations`. + */ +export const rhdhApisModule = createFrontendModule({ + pluginId: 'app', + extensions: [storageApi, learningPathApi, catalogGraphApi], +}); diff --git a/packages/app-next/src/hooks/useThemedConfig.test.tsx b/packages/app-next/src/hooks/useThemedConfig.test.tsx new file mode 100644 index 0000000000..62eadc08f0 --- /dev/null +++ b/packages/app-next/src/hooks/useThemedConfig.test.tsx @@ -0,0 +1,93 @@ +/* + * Layer 3 component test mirroring the behavior of the custom-theme UI E2E + * spec (net additive — the E2E spec is left in place). Covers how branding + * assets and sidebar styling are selected from config and the active theme. + */ +import { PropsWithChildren } from 'react'; + +import { configApiRef } from '@backstage/core-plugin-api'; +import { mockApis, TestApiProvider } from '@backstage/frontend-test-utils'; + +import { createTheme, ThemeOptions, ThemeProvider } from '@mui/material/styles'; +import { renderHook } from '@testing-library/react'; + +import { + useAppBarBackgroundScheme, + useAppBarThemedConfig, +} from './useThemedConfig'; + +const themeWith = (rhdhGeneral?: object) => + createTheme( + rhdhGeneral + ? ({ + palette: { rhdh: { general: rhdhGeneral } }, + } as unknown as ThemeOptions) + : {}, + ); + +const makeWrapper = (rhdhGeneral: object | undefined, brandingData: object) => { + const theme = themeWith(rhdhGeneral); + const configApi = mockApis.config({ data: brandingData }); + + return ({ children }: PropsWithChildren) => ( + + + {children} + + + ); +}; + +describe('useAppBarBackgroundScheme', () => { + it('returns the scheme configured on the theme palette', () => { + const { result } = renderHook(() => useAppBarBackgroundScheme(), { + wrapper: makeWrapper({ appBarBackgroundScheme: 'light' }, {}), + }); + + expect(result.current).toEqual('light'); + }); + + it("defaults to 'dark' when the theme does not set a scheme", () => { + const { result } = renderHook(() => useAppBarBackgroundScheme(), { + wrapper: makeWrapper(undefined, {}), + }); + + expect(result.current).toEqual('dark'); + }); +}); + +describe('useAppBarThemedConfig', () => { + it('returns a string branding asset unchanged', () => { + const { result } = renderHook( + () => useAppBarThemedConfig('app.branding.fullLogo'), + { + wrapper: makeWrapper( + { appBarBackgroundScheme: 'light' }, + { app: { branding: { fullLogo: 'logo.svg' } } }, + ), + }, + ); + + expect(result.current).toEqual('logo.svg'); + }); + + it('selects the branding variant matching the app-bar scheme', () => { + const { result } = renderHook( + () => useAppBarThemedConfig('app.branding.fullLogo'), + { + wrapper: makeWrapper( + { appBarBackgroundScheme: 'dark' }, + { + app: { + branding: { + fullLogo: { light: 'logo-light.svg', dark: 'logo-dark.svg' }, + }, + }, + }, + ), + }, + ); + + expect(result.current).toEqual('logo-dark.svg'); + }); +}); diff --git a/packages/app-next/src/hooks/useThemedConfig.ts b/packages/app-next/src/hooks/useThemedConfig.ts new file mode 100644 index 0000000000..0edfec55a3 --- /dev/null +++ b/packages/app-next/src/hooks/useThemedConfig.ts @@ -0,0 +1,39 @@ +import { configApiRef, useApi } from '@backstage/core-plugin-api'; + +import { useTheme } from '@mui/material/styles'; +import type { ThemeConfig } from '@red-hat-developer-hub/backstage-plugin-theme'; + +import type { Config } from '../../config'; + +type fullLogoType = NonNullable['fullLogo']; +type iconLogoType = NonNullable['fullLogo']; + +/** + * Get the app bar background scheme from the theme. Defaults to 'dark' if not set. + */ +export const useAppBarBackgroundScheme = () => { + const theme = useTheme(); + + return ( + (theme as ThemeConfig)?.palette?.rhdh?.general?.appBarBackgroundScheme ?? + 'dark' + ); +}; + +/** + * Gets a config value based on the value of `theme.palette.rhdh.general.appBarBackgroundScheme`. + */ +export const useAppBarThemedConfig = ( + key: 'app.branding.fullLogo' | 'app.branding.iconLogo', +) => { + const appBarBackgroundScheme = useAppBarBackgroundScheme(); + + const configApi = useApi(configApiRef); + + /** The fullLogo config specified by Red Hat Developer Hub */ + const fullLogo = configApi.getOptional(key); + + return typeof fullLogo === 'string' + ? fullLogo + : fullLogo?.[appBarBackgroundScheme]; +}; diff --git a/packages/app-next/src/hooks/useTranslation.ts b/packages/app-next/src/hooks/useTranslation.ts new file mode 100644 index 0000000000..3bfd9ba7ec --- /dev/null +++ b/packages/app-next/src/hooks/useTranslation.ts @@ -0,0 +1,10 @@ +import { + TranslationFunction, + useTranslationRef, +} from '@backstage/core-plugin-api/alpha'; + +import { rhdhTranslationRef } from '../translations/rhdh/ref'; + +export const useTranslation = (): { + t: TranslationFunction; +} => useTranslationRef(rhdhTranslationRef); diff --git a/packages/app-next/src/modules/nav/LogoFull.tsx b/packages/app-next/src/modules/nav/LogoFull.tsx new file mode 100644 index 0000000000..4d479bd2e3 --- /dev/null +++ b/packages/app-next/src/modules/nav/LogoFull.tsx @@ -0,0 +1,80 @@ +import { useAppBarBackgroundScheme } from '../../hooks/useThemedConfig'; + +export const LogoFull = (props: React.ComponentProps<'svg'>) => { + const appBarBackgroundScheme = useAppBarBackgroundScheme(); + + return ( + + + + + + + + + ); +}; diff --git a/packages/app-next/src/modules/nav/LogoIcon.tsx b/packages/app-next/src/modules/nav/LogoIcon.tsx new file mode 100644 index 0000000000..10b780d799 --- /dev/null +++ b/packages/app-next/src/modules/nav/LogoIcon.tsx @@ -0,0 +1,19 @@ +export const LogoIcon = (props: React.ComponentProps<'svg'>) => { + return ( + + + + + ); +}; diff --git a/packages/app-next/src/modules/nav/Sidebar.tsx b/packages/app-next/src/modules/nav/Sidebar.tsx new file mode 100644 index 0000000000..89625f2160 --- /dev/null +++ b/packages/app-next/src/modules/nav/Sidebar.tsx @@ -0,0 +1,70 @@ +import { + Sidebar, + SidebarDivider, + SidebarGroup, + SidebarItem, + SidebarScrollWrapper, + SidebarSpace, +} from '@backstage/core-components'; +import { NavContentBlueprint } from '@backstage/plugin-app-react'; +import { SidebarSearchModal } from '@backstage/plugin-search'; +import { UserSettingsSignInAvatar } from '@backstage/plugin-user-settings'; +import { useAppDrawer } from '@red-hat-developer-hub/backstage-plugin-app-react'; +import ChatIcon from '@mui/icons-material/Chat'; +import MenuIcon from '@mui/icons-material/Menu'; +import SearchIcon from '@mui/icons-material/Search'; + +import { SidebarLogo } from './SidebarLogo'; + +const ChatDrawerItem = () => { + const { toggleDrawer } = useAppDrawer(); + return ( + } + text="Chat" + onClick={() => toggleDrawer('lightspeed')} + /> + ); +}; + +export const SidebarContent = NavContentBlueprint.make({ + params: { + component: ({ navItems }) => { + const nav = navItems.withComponent(item => ( + item.icon} to={item.href} text={item.title} /> + )); + + nav.take('page:search'); + + return ( + + + } to="/search"> + + + + }> + {nav.take('page:catalog')} + {nav.take('page:scaffolder')} + + + {nav.rest({ sortBy: 'title' })} + + + + + + + } + to="/settings" + > + {nav.take('page:app-visualizer')} + {nav.take('page:user-settings')} + + + ); + }, + }, +}); diff --git a/packages/app-next/src/modules/nav/SidebarLogo.test.tsx b/packages/app-next/src/modules/nav/SidebarLogo.test.tsx new file mode 100644 index 0000000000..f38871414f --- /dev/null +++ b/packages/app-next/src/modules/nav/SidebarLogo.test.tsx @@ -0,0 +1,122 @@ +import { BrowserRouter } from 'react-router-dom'; + +import { useSidebarOpenState } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +import { render } from '@testing-library/react'; + +import { useAppBarThemedConfig } from '../../hooks/useThemedConfig'; +import { useTranslation } from '../../hooks/useTranslation'; +import { SidebarLogo } from './SidebarLogo'; + +jest.mock('@backstage/core-components', () => ({ + ...jest.requireActual('@backstage/core-components'), + useSidebarOpenState: jest.fn(), +})); + +jest.mock('@backstage/core-plugin-api', () => ({ + ...jest.requireActual('@backstage/core-plugin-api'), + useApi: jest.fn(), +})); + +jest.mock('../../hooks/useThemedConfig', () => ({ + ...jest.requireActual('../../hooks/useThemedConfig'), + useAppBarThemedConfig: jest.fn(), +})); + +jest.mock('../../hooks/useTranslation', () => ({ + useTranslation: jest.fn(), +})); + +describe('SidebarLogo', () => { + beforeEach(() => { + // Mock translation function for all tests + (useTranslation as any).mockReturnValue({ + t: jest.fn((key: string) => { + const translations: Record = { + 'sidebar.home': 'Home', + 'sidebar.homeLogo': 'Home logo', + }; + return translations[key] || key; + }), + }); + }); + + it('when sidebar is open renders the component with full logo base64 provided by config', () => { + (useApi as any).mockReturnValue({ + getOptional: jest.fn().mockReturnValue('fullLogoWidth'), + }); + (useAppBarThemedConfig as any).mockReturnValue('fullLogoBase64URI'); + + (useSidebarOpenState as any).mockReturnValue({ isOpen: true }); + const { getByTestId } = render( + + + , + ); + + const fullLogo = getByTestId('home-logo'); + expect(fullLogo).toBeInTheDocument(); + expect(fullLogo).toHaveAttribute('src', 'fullLogoBase64URI'); + expect(fullLogo).toHaveAttribute('alt', 'Home logo'); + + const logoLink = fullLogo.closest('a'); + expect(logoLink).toHaveAttribute('aria-label', 'Home'); + }); + + it('when sidebar is open renders the component with default full logo if config is undefined', () => { + (useApi as any).mockReturnValue({ + getOptional: jest.fn().mockReturnValue(undefined), + }); + + (useAppBarThemedConfig as any).mockReturnValue(undefined); + + (useSidebarOpenState as any).mockReturnValue({ isOpen: true }); + const { getByTestId } = render( + + + , + ); + + expect(getByTestId('app-next-full-logo')).toBeInTheDocument(); + }); + + it('when sidebar is closed renders the component with icon logo base64 provided by config', () => { + (useApi as any).mockReturnValue({ + getOptional: jest.fn().mockReturnValue('fullLogoWidth'), + }); + (useAppBarThemedConfig as any).mockReturnValue('iconLogoBase64URI'); + + (useSidebarOpenState as any).mockReturnValue({ isOpen: false }); + const { getByTestId } = render( + + + , + ); + + const fullLogo = getByTestId('home-logo'); + expect(fullLogo).toBeInTheDocument(); + expect(fullLogo).toHaveAttribute('src', 'iconLogoBase64URI'); + expect(fullLogo).toHaveAttribute('alt', 'Home logo'); + + const logoLink = fullLogo.closest('a'); + expect(logoLink).toHaveAttribute('aria-label', 'Home'); + }); + + it('when sidebar is closed renders the component with icon logo from default if not provided with config', () => { + (useApi as any).mockReturnValue({ + getOptional: jest.fn().mockReturnValue(undefined), + }); + + (useAppBarThemedConfig as any).mockReturnValue(undefined); + + (useSidebarOpenState as any).mockReturnValue({ isOpen: false }); + const { getByTestId } = render( + + + , + ); + + expect(getByTestId('app-next-icon-logo')).toBeInTheDocument(); + }); +}); diff --git a/packages/app-next/src/modules/nav/SidebarLogo.tsx b/packages/app-next/src/modules/nav/SidebarLogo.tsx new file mode 100644 index 0000000000..899ca3244c --- /dev/null +++ b/packages/app-next/src/modules/nav/SidebarLogo.tsx @@ -0,0 +1,73 @@ +import type { ComponentType } from 'react'; + +import { Link, useSidebarOpenState } from '@backstage/core-components'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; + +import { makeStyles } from 'tss-react/mui'; + +import { useAppBarThemedConfig } from '../../hooks/useThemedConfig'; +import { useTranslation } from '../../hooks/useTranslation'; +import { LogoFull } from './LogoFull'; +import { LogoIcon } from './LogoIcon'; + +const useStyles = makeStyles()({ + sidebarLogo: { + margin: '24px 0px 6px 24px', + }, +}); + +const LogoRender = ({ + base64Logo, + DefaultLogo, + width, + altText, +}: { + base64Logo: string | undefined; + DefaultLogo: ComponentType>; + width: string | number; + altText: string; +}) => { + return base64Logo ? ( + {altText} + ) : ( + + ); +}; + +export const SidebarLogo = () => { + const { classes } = useStyles(); + const { isOpen } = useSidebarOpenState(); + + const { t } = useTranslation(); + const configApi = useApi(configApiRef); + + const logoFullBase64URI = useAppBarThemedConfig('app.branding.fullLogo'); + + const fullLogoWidth = configApi + .getOptional('app.branding.fullLogoWidth') + ?.toString(); + + const logoIconBase64URI = useAppBarThemedConfig('app.branding.iconLogo'); + + return ( +
+ + {isOpen ? ( + + ) : ( + + )} + +
+ ); +}; diff --git a/packages/app-next/src/modules/nav/index.ts b/packages/app-next/src/modules/nav/index.ts new file mode 100644 index 0000000000..2b9d217586 --- /dev/null +++ b/packages/app-next/src/modules/nav/index.ts @@ -0,0 +1,16 @@ +import { createFrontendModule } from '@backstage/frontend-plugin-api'; +import { SidebarContent } from './Sidebar'; + +/** + * RHDH-branded sidebar (logo, search, menu ordering, drawer toggle, settings). + * Overrides the upstream `nav-content:app` extension. + * + * This module is intentionally kept local to `app-next` rather than + * upstreamed to `rhdh-plugins/workspaces/app-defaults`. See + * docs/adr/0001-app-next-sidebar-module-location.md for the rationale and + * the future-upstream follow-up. + */ +export const navModule = createFrontendModule({ + pluginId: 'app', + extensions: [SidebarContent], +}); diff --git a/packages/app-next/src/translations/catalog-import/catalog-import-en.ts b/packages/app-next/src/translations/catalog-import/catalog-import-en.ts new file mode 100644 index 0000000000..cb03a2538b --- /dev/null +++ b/packages/app-next/src/translations/catalog-import/catalog-import-en.ts @@ -0,0 +1,13 @@ +import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import'; + +const en = createTranslationMessages({ + ref: catalogImportTranslationRef, + full: false, // False means that this is a partial translation + messages: { + 'defaultImportPage.headerTitle': 'Import an existing Git repository', + 'importInfoCard.title': 'Import an existing Git repository', + }, +}); + +export default en; diff --git a/packages/app-next/src/translations/catalog-import/catalog-import.ts b/packages/app-next/src/translations/catalog-import/catalog-import.ts new file mode 100644 index 0000000000..6c4ceb029b --- /dev/null +++ b/packages/app-next/src/translations/catalog-import/catalog-import.ts @@ -0,0 +1,14 @@ +import { createTranslationResource } from '@backstage/core-plugin-api/alpha'; +import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import'; + +export const catalogImportTranslations = createTranslationResource({ + ref: catalogImportTranslationRef, + translations: { + de: () => import('./de'), + en: () => import('./catalog-import-en'), + es: () => import('./es'), + fr: () => import('./fr'), + it: () => import('./it'), + ja: () => import('./ja'), + }, +}); diff --git a/packages/app-next/src/translations/catalog-import/de.ts b/packages/app-next/src/translations/catalog-import/de.ts new file mode 100644 index 0000000000..7e64dee77d --- /dev/null +++ b/packages/app-next/src/translations/catalog-import/de.ts @@ -0,0 +1,29 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import'; + +const de = createTranslationMessages({ + ref: catalogImportTranslationRef, + full: false, + messages: { + 'defaultImportPage.headerTitle': 'Vorhandenes Git-Repository importieren', + 'importInfoCard.title': 'Vorhandenes Git-Repository importieren', + }, +}); + +export default de; diff --git a/packages/app-next/src/translations/catalog-import/es.ts b/packages/app-next/src/translations/catalog-import/es.ts new file mode 100644 index 0000000000..728b1440a4 --- /dev/null +++ b/packages/app-next/src/translations/catalog-import/es.ts @@ -0,0 +1,29 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import'; + +const es = createTranslationMessages({ + ref: catalogImportTranslationRef, + full: false, + messages: { + 'defaultImportPage.headerTitle': 'Importar un repositorio Git existente', + 'importInfoCard.title': 'Importar un repositorio Git existente', + }, +}); + +export default es; diff --git a/packages/app-next/src/translations/catalog-import/fr.ts b/packages/app-next/src/translations/catalog-import/fr.ts new file mode 100644 index 0000000000..a11f73da5a --- /dev/null +++ b/packages/app-next/src/translations/catalog-import/fr.ts @@ -0,0 +1,130 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import'; + +export default createTranslationMessages({ + ref: catalogImportTranslationRef, + full: false, + messages: { + 'defaultImportPage.headerTitle': 'Importer un dépôt Git existant', + 'importInfoCard.title': 'Importer un dépôt Git existant', + 'buttons.back': 'Arrière', + 'defaultImportPage.contentHeaderTitle': + 'Commencez à suivre votre composant dans {{appTitle}}', + 'defaultImportPage.supportTitle': + "Commencez à suivre votre composant dans {{appTitle}} en l'ajoutant au catalogue de logiciels.", + 'importInfoCard.deepLinkTitle': + 'En savoir plus sur le catalogue de logiciels', + 'importInfoCard.linkDescription': + "Saisissez l'URL de votre référentiel de code source pour l'ajouter à {{appTitle}}.", + 'importInfoCard.fileLinkTitle': "Lien vers un fichier d'entité existant", + 'importInfoCard.examplePrefix': 'Exemple: ', + 'importInfoCard.fileLinkDescription': + "L'assistant analyse le fichier, prévisualise les entités et les ajoute au catalogue {{appTitle}}.", + 'importInfoCard.githubIntegration.title': 'Lien vers un référentiel', + 'importInfoCard.githubIntegration.label': 'GitHub uniquement', + 'importInfoCard.exampleDescription': + "L'assistant découvre tous les fichiers {{catalogFilename}} dans le référentiel, prévisualise les entités et les ajoute au catalogue {{appTitle}}.", + 'importInfoCard.preparePullRequestDescription': + "Si aucune entité n'est trouvée, l'assistant préparera une Pull Request qui ajoute un exemple de {{catalogFilename}} et prépare le catalogue {{appTitle}} pour charger toutes les entités dès que la Pull Request est fusionnée.", + 'importStepper.singleLocation.title': 'Sélectionner des emplacements', + 'importStepper.singleLocation.description': 'Lieux découverts : 1', + 'importStepper.multipleLocations.title': 'Sélectionner des emplacements', + 'importStepper.multipleLocations.description': + 'Lieux découverts : {{length, number}}', + 'importStepper.noLocation.title': "Créer une demande d'extraction", + 'importStepper.noLocation.createPr.detailsTitle': + "Détails de la demande d'extraction", + 'importStepper.noLocation.createPr.titleLabel': + "Titre de la demande d'extraction", + 'importStepper.noLocation.createPr.titlePlaceholder': + "Ajouter des fichiers descripteurs d'entités de catalogue Backstage", + 'importStepper.noLocation.createPr.bodyLabel': + "Corps de la demande d'extraction", + 'importStepper.noLocation.createPr.bodyPlaceholder': + 'Un texte descriptif avec prise en charge Markdown', + 'importStepper.noLocation.createPr.configurationTitle': + "Configuration de l'entité", + 'importStepper.noLocation.createPr.componentNameLabel': + 'Nom du composant créé', + 'importStepper.noLocation.createPr.componentNamePlaceholder': + 'mon-composant', + 'importStepper.noLocation.createPr.ownerLoadingText': + 'Chargement des groupes…', + 'importStepper.noLocation.createPr.ownerHelperText': + 'Sélectionnez un propriétaire dans la liste ou entrez une référence à un groupe ou à un utilisateur', + 'importStepper.noLocation.createPr.ownerErrorHelperText': 'valeur requise', + 'importStepper.noLocation.createPr.ownerLabel': "Propriétaire de l'entité", + 'importStepper.noLocation.createPr.ownerPlaceholder': 'mon-groupe', + 'importStepper.noLocation.createPr.codeownersHelperText': + "AVERTISSEMENT : cette opération peut échouer si aucun fichier CODEOWNERS n'est trouvé à l'emplacement cible.", + 'importStepper.analyze.title': "Sélectionnez l'URL", + 'importStepper.prepare.title': "Actions d'importation", + 'importStepper.prepare.description': 'Facultatif', + 'importStepper.review.title': 'Revoir', + 'importStepper.finish.title': 'Finition', + 'stepFinishImportLocation.backButtonText': 'Enregistrer un autre', + 'stepFinishImportLocation.repository.title': + 'La Pull Request suivante a été ouverte : ', + 'stepFinishImportLocation.repository.description': + 'Vos entités seront importées dès que la Pull Request sera fusionnée.', + 'stepFinishImportLocation.locations.new': + 'Les entités suivantes ont été ajoutées au catalogue :', + 'stepFinishImportLocation.locations.existing': + 'Une actualisation a été déclenchée pour les emplacements suivants :', + 'stepFinishImportLocation.locations.viewButtonText': + 'Afficher le composant', + 'stepFinishImportLocation.locations.backButtonText': 'Enregistrer un autre', + 'stepInitAnalyzeUrl.error.repository': + 'Impossible de générer des entités pour votre référentiel', + 'stepInitAnalyzeUrl.error.locations': + "Il n'y a aucune entité à cet endroit", + 'stepInitAnalyzeUrl.error.default': + "Résultat d'analyse inconnu reçu de type {{type}}. Veuillez contacter l'équipe d'assistance.", + 'stepInitAnalyzeUrl.error.url': 'Doit commencer par http:// ou https://.', + 'stepInitAnalyzeUrl.urlHelperText': + "Saisissez le chemin complet vers votre fichier d'entité pour commencer à suivre votre composant", + 'stepInitAnalyzeUrl.nextButtonText': 'Analyser', + 'stepPrepareCreatePullRequest.description': + "Vous avez entré un lien vers un référentiel {{integrationType}} mais aucun {{catalogFilename}} n'a pu être trouvé. Utilisez ce formulaire pour ouvrir une Pull Request qui en crée une.", + 'stepPrepareCreatePullRequest.previewPr.title': + "Aperçu de la demande d'extraction", + 'stepPrepareCreatePullRequest.previewPr.subheader': + 'Créer une nouvelle Pull Request', + 'stepPrepareCreatePullRequest.previewCatalogInfo.title': + 'Aperçu des entités', + 'stepPrepareCreatePullRequest.nextButtonText': + 'Créer des relations publiques', + 'stepPrepareSelectLocations.locations.description': + 'Sélectionnez un ou plusieurs emplacements présents dans votre dépôt git :', + 'stepPrepareSelectLocations.locations.selectAll': 'Sélectionner tout', + 'stepPrepareSelectLocations.existingLocations.description': + 'Ces emplacements existent déjà dans le catalogue :', + 'stepPrepareSelectLocations.nextButtonText': 'Revoir', + 'stepReviewLocation.prepareResult.title': + 'La Pull Request suivante a été ouverte : ', + 'stepReviewLocation.prepareResult.description': + "Vous pouvez déjà importer l'emplacement et {{appTitle}} récupérera les entités dès que la Pull Request sera fusionnée.", + 'stepReviewLocation.catalog.exists': + 'Les emplacements suivants existent déjà dans le catalogue :', + 'stepReviewLocation.catalog.new': + 'Les entités suivantes seront ajoutées au catalogue :', + 'stepReviewLocation.refresh': 'Rafraîchir', + 'stepReviewLocation.import': 'Importer', + }, +}); diff --git a/packages/app-next/src/translations/catalog-import/it.ts b/packages/app-next/src/translations/catalog-import/it.ts new file mode 100644 index 0000000000..7b34c08fd6 --- /dev/null +++ b/packages/app-next/src/translations/catalog-import/it.ts @@ -0,0 +1,27 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import'; + +export default createTranslationMessages({ + ref: catalogImportTranslationRef, + full: false, + messages: { + 'defaultImportPage.headerTitle': 'Importa un repository Git esistente', + 'importInfoCard.title': 'Importa un repository Git esistente', + }, +}); diff --git a/packages/app-next/src/translations/catalog-import/ja.ts b/packages/app-next/src/translations/catalog-import/ja.ts new file mode 100644 index 0000000000..2b76366b2f --- /dev/null +++ b/packages/app-next/src/translations/catalog-import/ja.ts @@ -0,0 +1,27 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import'; + +export default createTranslationMessages({ + ref: catalogImportTranslationRef, + full: false, + messages: { + 'defaultImportPage.headerTitle': '既存の Git リポジトリーのインポート', + 'importInfoCard.title': '既存の Git リポジトリーのインポート', + }, +}); diff --git a/packages/app-next/src/translations/catalog/catalog-en.ts b/packages/app-next/src/translations/catalog/catalog-en.ts new file mode 100644 index 0000000000..3e7a2ac7cc --- /dev/null +++ b/packages/app-next/src/translations/catalog/catalog-en.ts @@ -0,0 +1,12 @@ +import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { catalogTranslationRef } from '@backstage/plugin-catalog'; + +const en = createTranslationMessages({ + ref: catalogTranslationRef, + full: false, // False means that this is a partial translation + messages: { + 'indexPage.createButtonTitle': 'Self-service', + }, +}); + +export default en; diff --git a/packages/app-next/src/translations/catalog/catalog.ts b/packages/app-next/src/translations/catalog/catalog.ts new file mode 100644 index 0000000000..07fabb92bd --- /dev/null +++ b/packages/app-next/src/translations/catalog/catalog.ts @@ -0,0 +1,14 @@ +import { createTranslationResource } from '@backstage/core-plugin-api/alpha'; +import { catalogTranslationRef } from '@backstage/plugin-catalog'; + +export const catalogTranslations = createTranslationResource({ + ref: catalogTranslationRef, + translations: { + de: () => import('./de'), + en: () => import('./catalog-en'), + es: () => import('./es'), + fr: () => import('./fr'), + it: () => import('./it'), + ja: () => import('./ja'), + }, +}); diff --git a/packages/app-next/src/translations/catalog/de.ts b/packages/app-next/src/translations/catalog/de.ts new file mode 100644 index 0000000000..01720f0024 --- /dev/null +++ b/packages/app-next/src/translations/catalog/de.ts @@ -0,0 +1,26 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { catalogTranslationRef } from '@backstage/plugin-catalog'; + +export default createTranslationMessages({ + ref: catalogTranslationRef, + full: false, + messages: { + 'indexPage.createButtonTitle': 'Self-Service', + }, +}); diff --git a/packages/app-next/src/translations/catalog/es.ts b/packages/app-next/src/translations/catalog/es.ts new file mode 100644 index 0000000000..9f6fffdaa9 --- /dev/null +++ b/packages/app-next/src/translations/catalog/es.ts @@ -0,0 +1,26 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { catalogTranslationRef } from '@backstage/plugin-catalog'; + +export default createTranslationMessages({ + ref: catalogTranslationRef, + full: false, + messages: { + 'indexPage.createButtonTitle': 'Autoservicio', + }, +}); diff --git a/packages/app-next/src/translations/catalog/fr.ts b/packages/app-next/src/translations/catalog/fr.ts new file mode 100644 index 0000000000..e8d29553a1 --- /dev/null +++ b/packages/app-next/src/translations/catalog/fr.ts @@ -0,0 +1,120 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { catalogTranslationRef } from '@backstage/plugin-catalog'; + +export default createTranslationMessages({ + ref: catalogTranslationRef, + full: false, + messages: { + 'indexPage.createButtonTitle': 'Self-service', + 'indexPage.title': 'Catalogue {{orgName}}', + 'indexPage.supportButtonContent': + 'Toutes les entités de votre catalogue de logiciels', + 'aboutCard.title': 'À propos', + 'aboutCard.refreshButtonTitle': + "Actualisation de l'entité de planification", + 'aboutCard.editButtonTitle': 'Modifier les métadonnées', + 'aboutCard.createSimilarButtonTitle': 'Créer quelque chose de similaire', + 'aboutCard.refreshScheduledMessage': 'Actualisation programmée', + 'aboutCard.launchTemplate': 'Modèle de lancement', + 'aboutCard.viewTechdocs': 'Voir TechDocs', + 'aboutCard.viewSource': 'Voir la source', + 'aboutCard.descriptionField.label': 'Description', + 'aboutCard.descriptionField.value': 'Aucune description', + 'aboutCard.ownerField.label': 'Propriétaire', + 'aboutCard.ownerField.value': 'Pas de propriétaire', + 'aboutCard.domainField.label': 'Domaine', + 'aboutCard.domainField.value': 'Pas de domaine', + 'aboutCard.systemField.label': 'Système', + 'aboutCard.systemField.value': 'Pas de système', + 'aboutCard.parentComponentField.label': 'Composant parent', + 'aboutCard.parentComponentField.value': 'Aucun composant parent', + 'aboutCard.typeField.label': 'Taper', + 'aboutCard.lifecycleField.label': 'Cycle de vie', + 'aboutCard.tagsField.label': 'Mots-clés', + 'aboutCard.tagsField.value': 'Aucune balise', + 'aboutCard.targetsField.label': 'Cibles', + 'searchResultItem.lifecycle': 'Cycle de vie', + 'searchResultItem.owner': 'Propriétaire', + 'catalogTable.warningPanelTitle': + 'Impossible de récupérer les entités du catalogue.', + 'catalogTable.viewActionTitle': 'Voir', + 'catalogTable.editActionTitle': 'Modifier', + 'catalogTable.starActionTitle': 'Ajouter aux favoris', + 'catalogTable.unStarActionTitle': 'Supprimer des favoris', + 'dependencyOfComponentsCard.title': 'Dépendance des composants', + 'dependencyOfComponentsCard.emptyMessage': + 'Aucun composant ne dépend de ce composant', + 'dependsOnComponentsCard.title': 'Cela dépend des composants', + 'dependsOnComponentsCard.emptyMessage': + "Aucun composant n'est une dépendance de ce composant", + 'dependsOnResourcesCard.title': 'Cela dépend des ressources', + 'dependsOnResourcesCard.emptyMessage': + "Aucune ressource n'est une dépendance de ce composant", + 'entityContextMenu.copiedMessage': 'Copié!', + 'entityContextMenu.moreButtonTitle': 'Plus', + 'entityContextMenu.inspectMenuTitle': "Inspecter l'entité", + 'entityContextMenu.copyURLMenuTitle': "Copier l'URL de l'entité", + 'entityContextMenu.unregisterMenuTitle': "Désenregistrer l'entité", + 'entityLabelsCard.title': 'Étiquettes', + 'entityLabelsCard.emptyDescription': + "Aucune étiquette définie pour cette entité. Vous pouvez ajouter des étiquettes à votre entité YAML comme indiqué dans l'exemple en surbrillance ci-dessous :", + 'entityLabelsCard.readMoreButtonTitle': 'En savoir plus', + 'entityLabels.warningPanelTitle': 'Entité non trouvée', + 'entityLabels.ownerLabel': 'Propriétaire', + 'entityLabels.lifecycleLabel': 'Cycle de vie', + 'entityLinksCard.title': 'Links', + 'entityLinksCard.emptyDescription': + "Aucun lien défini pour cette entité. Vous pouvez ajouter des liens vers votre entité YAML comme indiqué dans l'exemple en surbrillance ci-dessous :", + 'entityLinksCard.readMoreButtonTitle': 'En savoir plus', + 'entityNotFound.title': "L'entité n'a pas été trouvée", + 'entityNotFound.description': + 'Vous voulez nous aider à construire cela ? Consultez notre documentation de mise en route.', + 'entityNotFound.docButtonTitle': 'DOCUMENTS', + 'deleteEntity.dialogTitle': + 'Êtes-vous sûr de vouloir supprimer cette entité ?', + 'deleteEntity.deleteButtonTitle': 'Supprimer', + 'deleteEntity.cancelButtonTitle': 'Annuler', + 'deleteEntity.description': + "Cette entité n'est référencée par aucun emplacement et ne reçoit donc pas de mises à jour. Cliquez ici pour supprimer.", + entityProcessingErrorsDescription: "L'erreur ci-dessous provient de", + entityRelationWarningDescription: + "Cette entité a des relations avec d'autres entités, qui ne peuvent pas être trouvées dans le catalogue. Les entités non trouvées sont : ", + 'hasComponentsCard.title': 'Contient des composants', + 'hasComponentsCard.emptyMessage': + 'Aucun composant ne fait partie de ce système', + 'hasResourcesCard.title': 'Dispose de ressources', + 'hasResourcesCard.emptyMessage': + 'Aucune ressource ne fait partie de ce système', + 'hasSubcomponentsCard.title': 'A des sous-composants', + 'hasSubcomponentsCard.emptyMessage': + 'Aucun sous-composant ne fait partie de ce composant', + 'hasSubdomainsCard.title': 'A des sous-domaines', + 'hasSubdomainsCard.emptyMessage': + 'Aucun sous-domaine ne fait partie de ce domaine', + 'hasSystemsCard.title': 'A des systèmes', + 'hasSystemsCard.emptyMessage': 'Aucun système ne fait partie de ce domaine', + 'relatedEntitiesCard.emptyHelpLinkTitle': 'Apprenez à changer cela', + 'systemDiagramCard.title': 'Diagramme du système', + 'systemDiagramCard.description': + 'Utilisez le pincement et le zoom pour vous déplacer dans le diagramme.', + 'systemDiagramCard.edgeLabels.partOf': 'une partie de', + 'systemDiagramCard.edgeLabels.provides': 'fournit', + 'systemDiagramCard.edgeLabels.dependsOn': 'dépend de', + }, +}); diff --git a/packages/app-next/src/translations/catalog/it.ts b/packages/app-next/src/translations/catalog/it.ts new file mode 100644 index 0000000000..08c30b89d8 --- /dev/null +++ b/packages/app-next/src/translations/catalog/it.ts @@ -0,0 +1,26 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { catalogTranslationRef } from '@backstage/plugin-catalog'; + +export default createTranslationMessages({ + ref: catalogTranslationRef, + full: false, + messages: { + 'indexPage.createButtonTitle': 'Self-service', + }, +}); diff --git a/packages/app-next/src/translations/catalog/ja.ts b/packages/app-next/src/translations/catalog/ja.ts new file mode 100644 index 0000000000..ae708f2773 --- /dev/null +++ b/packages/app-next/src/translations/catalog/ja.ts @@ -0,0 +1,26 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { catalogTranslationRef } from '@backstage/plugin-catalog'; + +export default createTranslationMessages({ + ref: catalogTranslationRef, + full: false, + messages: { + 'indexPage.createButtonTitle': 'セルフサービス', + }, +}); diff --git a/packages/app-next/src/translations/core-components/core-components-en.ts b/packages/app-next/src/translations/core-components/core-components-en.ts new file mode 100644 index 0000000000..36767f8e80 --- /dev/null +++ b/packages/app-next/src/translations/core-components/core-components-en.ts @@ -0,0 +1,55 @@ +import { coreComponentsTranslationRef } from '@backstage/core-components'; +import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; + +const en = createTranslationMessages({ + ref: coreComponentsTranslationRef, + full: false, // False means that this is a partial translation + messages: { + // This is a workaround that ensures that multiple translations + // of the shared core-components are present that was added over time. + // See: + // https://issues.redhat.com/browse/RHDHBUGS-1235 + // https://issues.redhat.com/browse/RHDHBUGS-1976 + // + // For example, the 'table.header.actions' key was introduced in + // @backstage/core-component 0.17.3 (part of Backstage 1.40.0) + // and wasn't there in 0.17.2 (part of Backstage 1.39.0). + // + // See: + // https://github.com/backstage/backstage/blob/v1.39.0/packages/core-components/src/translation.ts#L87-L107 + // https://github.com/backstage/backstage/blob/v1.40.0/packages/core-components/src/translation.ts#L87-L110 + // https://github.com/backstage/versions/blob/main/v1/releases/1.39.0/manifest.json#L80-L83 + // https://github.com/backstage/versions/blob/main/v1/releases/1.40.0/manifest.json#L80-L83 + // + // This here is a workaround that ensures that at least these translations + // are available also if different plugins brings their own version of @backstage/core-components. + // + // In the future we should make sure that translations of multiple versions of the + // @backstage/core-components library are merged properly and are shipped with RHDH. + // We track that change here: https://issues.redhat.com/browse/RHIDP-8836 + // + // Added in Backstage 1.37 + 'table.filter.placeholder': 'All results', + 'table.body.emptyDataSourceMessage': 'No records to display', + 'table.pagination.firstTooltip': 'First Page', + 'table.pagination.labelDisplayedRows': '{from}-{to} of {count}', + 'table.pagination.labelRowsSelect': 'rows', + 'table.pagination.lastTooltip': 'Last Page', + 'table.pagination.nextTooltip': 'Next Page', + 'table.pagination.previousTooltip': 'Previous Page', + 'table.toolbar.search': 'Filter', + + // Changed in Backstage 1.38 + 'alertDisplay.message_one': '({{ count }} newer message)', + 'alertDisplay.message_other': '({{ count }} newer messages)', + + // Added in Backstage 1.40 + 'table.header.actions': 'Actions', + + // Added in Backstage 1.41 + 'oauthRequestDialog.message': + 'Sign-in to allow {{appTitle}} access to {{provider}} APIs and identities.', + } as any, +}); + +export default en; diff --git a/packages/app-next/src/translations/core-components/core-components.ts b/packages/app-next/src/translations/core-components/core-components.ts new file mode 100644 index 0000000000..b394d51fb6 --- /dev/null +++ b/packages/app-next/src/translations/core-components/core-components.ts @@ -0,0 +1,14 @@ +import { coreComponentsTranslationRef } from '@backstage/core-components'; +import { createTranslationResource } from '@backstage/core-plugin-api/alpha'; + +export const coreComponentsTranslations = createTranslationResource({ + ref: coreComponentsTranslationRef, + translations: { + de: () => import('./de'), + en: () => import('./core-components-en'), + es: () => import('./es'), + fr: () => import('./fr'), + it: () => import('./it'), + ja: () => import('./ja'), + }, +}); diff --git a/packages/app-next/src/translations/core-components/de.ts b/packages/app-next/src/translations/core-components/de.ts new file mode 100644 index 0000000000..14923c3542 --- /dev/null +++ b/packages/app-next/src/translations/core-components/de.ts @@ -0,0 +1,42 @@ +/* + * 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 { coreComponentsTranslationRef } from '@backstage/core-components'; +import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; + +const de = createTranslationMessages({ + ref: coreComponentsTranslationRef, + full: false, + messages: { + 'table.filter.placeholder': 'Alle Ergebnisse', + 'table.body.emptyDataSourceMessage': + 'Keine Datensätze zum Anzeigen vorhanden', + 'table.pagination.firstTooltip': 'Erste Seite', + 'table.pagination.labelDisplayedRows': '{von}-{bis} von {Anzahl}', + 'table.pagination.labelRowsSelect': 'Zeilen', + 'table.pagination.lastTooltip': 'Letzte Seite', + 'table.pagination.nextTooltip': 'Nächste Seite', + 'table.pagination.previousTooltip': 'Vorherige Seite', + 'table.toolbar.search': 'Filter', + 'alertDisplay.message_one': '({{ count }} neuere Nachricht)', + 'alertDisplay.message_other': '({{ count }} neuere Nachrichten)', + 'table.header.actions': 'Aktionen', + 'oauthRequestDialog.message': + 'Melden Sie sich an, um {{appTitle}} Zugriff auf die APIs und Identitäten von {{provider}} zu erlauben.', + }, +}); + +export default de; diff --git a/packages/app-next/src/translations/core-components/es.ts b/packages/app-next/src/translations/core-components/es.ts new file mode 100644 index 0000000000..92b933e968 --- /dev/null +++ b/packages/app-next/src/translations/core-components/es.ts @@ -0,0 +1,41 @@ +/* + * 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 { coreComponentsTranslationRef } from '@backstage/core-components'; +import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; + +const es = createTranslationMessages({ + ref: coreComponentsTranslationRef, + full: false, + messages: { + 'table.filter.placeholder': 'Todos los resultados', + 'table.body.emptyDataSourceMessage': 'No hay registros para mostrar', + 'table.pagination.firstTooltip': 'Primera página', + 'table.pagination.labelDisplayedRows': '{from}-{to} de {count}', + 'table.pagination.labelRowsSelect': 'filas', + 'table.pagination.lastTooltip': 'Última página', + 'table.pagination.nextTooltip': 'Página siguiente', + 'table.pagination.previousTooltip': 'Página anterior', + 'table.toolbar.search': 'Filtrar', + 'alertDisplay.message_one': '({{ count }} mensaje nuevo)', + 'alertDisplay.message_other': '({{ count }} mensajes nuevos)', + 'table.header.actions': 'Acciones', + 'oauthRequestDialog.message': + 'Inicie sesión para permitir que {{appTitle}} acceda a las API e identidades de {{provider}}.', + }, +}); + +export default es; diff --git a/packages/app-next/src/translations/core-components/fr.ts b/packages/app-next/src/translations/core-components/fr.ts new file mode 100644 index 0000000000..e4c2ac3757 --- /dev/null +++ b/packages/app-next/src/translations/core-components/fr.ts @@ -0,0 +1,87 @@ +/* + * 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 { coreComponentsTranslationRef } from '@backstage/core-components'; +import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; + +export default createTranslationMessages({ + ref: coreComponentsTranslationRef, + full: false, + messages: { + 'table.filter.placeholder': 'Tous les résultats', + 'table.body.emptyDataSourceMessage': 'Aucun enregistrement à afficher', + 'table.pagination.firstTooltip': 'Première page', + 'table.pagination.labelDisplayedRows': '{de}-{à} de {count}', + 'table.pagination.labelRowsSelect': 'rangées', + 'table.pagination.lastTooltip': 'Dernière page', + 'table.pagination.nextTooltip': 'Page suivante', + 'table.pagination.previousTooltip': 'Page précédente', + 'table.toolbar.search': 'Filtre', + 'alertDisplay.message_one': '({{ count }} message plus récent)', + 'alertDisplay.message_other': '({{ count }} messages plus récents)', + 'table.header.actions': 'Actes', + 'oauthRequestDialog.message': + 'Connectez-vous pour autoriser {{appTitle}} à accéder aux API et identités de {{provider}}.', + 'signIn.title': 'Se connecter', + 'signIn.loginFailed': 'La connexion a échoué', + 'signIn.customProvider.title': 'Utilisateur personnalisé', + 'signIn.customProvider.subtitle': + "Saisissez votre propre identifiant utilisateur et vos informations d'identification. Cette sélection ne sera pas enregistrée.", + 'signIn.customProvider.userId': "ID de l'utilisateur", + 'signIn.customProvider.tokenInvalid': + "Le jeton n'est pas un jeton JWT OpenID Connect valide", + 'signIn.customProvider.continue': 'Continuer', + 'signIn.customProvider.idToken': "Jeton d'identification (facultatif)", + 'signIn.guestProvider.title': 'Invité', + 'signIn.guestProvider.subtitle': + "Entrez en tant qu'utilisateur invité. Votre identité n'aura pas été vérifiée, ce qui signifie que certaines fonctionnalités pourraient ne pas être disponibles.", + 'signIn.guestProvider.enter': 'Entrer', + skipToContent: 'Accéder au contenu', + 'copyTextButton.tooltipText': 'Texte copié dans le presse-papiers', + 'simpleStepper.reset': 'Réinitialiser', + 'simpleStepper.finish': 'Finition', + 'simpleStepper.next': 'Suivant', + 'simpleStepper.skip': 'Ignorer', + 'simpleStepper.back': 'Arrière', + 'errorPage.subtitle': 'ERREUR {{status}} : {{statusMessage}}', + 'errorPage.title': "On dirait que quelqu'un a laissé tomber le micro !", + 'errorPage.goBack': 'Retour', + 'errorPage.showMoreDetails': 'Afficher plus de détails', + 'errorPage.showLessDetails': 'Afficher moins de détails', + 'emptyState.missingAnnotation.title': 'Annotation manquante', + 'emptyState.missingAnnotation.actionTitle': + "Ajoutez l'annotation à votre composant YAML comme indiqué dans l'exemple en surbrillance ci-dessous :", + 'emptyState.missingAnnotation.readMore': 'En savoir plus', + 'supportConfig.default.title': 'Support non configuré', + 'supportConfig.default.linkTitle': + 'Ajouter la clé de configuration « app.support »', + 'errorBoundary.title': + "Veuillez contacter {{slackChannel}} pour obtenir de l'aide.", + 'oauthRequestDialog.title': 'Connexion requise', + 'oauthRequestDialog.authRedirectTitle': + 'Cela déclenchera une redirection http vers la connexion OAuth.', + 'oauthRequestDialog.login': 'Connexion', + 'oauthRequestDialog.rejectAll': 'Tout rejeter', + 'supportButton.title': 'Support', + 'supportButton.close': 'Fermer', + 'table.filter.title': 'Filtres', + 'table.filter.clearAll': 'Tout effacer', + 'autoLogout.stillTherePrompt.title': "Déconnexion en raison d'inactivité", + 'autoLogout.stillTherePrompt.buttonText': 'Oui! Ne me déconnectez pas', + 'proxiedSignInPage.title': + 'Vous ne semblez pas être connecté. Veuillez essayer de recharger la page du navigateur.', + }, +}); diff --git a/packages/app-next/src/translations/core-components/it.ts b/packages/app-next/src/translations/core-components/it.ts new file mode 100644 index 0000000000..ad9f9558b7 --- /dev/null +++ b/packages/app-next/src/translations/core-components/it.ts @@ -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 { coreComponentsTranslationRef } from '@backstage/core-components'; +import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; + +export default createTranslationMessages({ + ref: coreComponentsTranslationRef, + full: false, + messages: { + 'table.filter.placeholder': 'Tutti i risultati', + 'table.body.emptyDataSourceMessage': 'Nessun record da visualizzare', + 'table.pagination.firstTooltip': 'Prima pagina', + 'table.pagination.labelDisplayedRows': '{from}-{to} di {count}', + 'table.pagination.labelRowsSelect': 'righe', + 'table.pagination.lastTooltip': 'Ultima pagina', + 'table.pagination.nextTooltip': 'Pagina successiva', + 'table.pagination.previousTooltip': 'Pagina precedente', + 'table.toolbar.search': 'Filtra', + 'alertDisplay.message_one': '({{ count }} messaggio più recente)', + 'alertDisplay.message_other': '({{ count }} messaggi più recenti)', + 'table.header.actions': 'Azioni', + 'oauthRequestDialog.message': + 'Accedere per consentire a {{appTitle}} di accedere alle API e alle identità di {{provider}}.', + }, +}); diff --git a/packages/app-next/src/translations/core-components/ja.ts b/packages/app-next/src/translations/core-components/ja.ts new file mode 100644 index 0000000000..20f0834ac7 --- /dev/null +++ b/packages/app-next/src/translations/core-components/ja.ts @@ -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 { coreComponentsTranslationRef } from '@backstage/core-components'; +import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; + +export default createTranslationMessages({ + ref: coreComponentsTranslationRef, + full: false, + messages: { + 'table.filter.placeholder': 'すべての結果', + 'table.body.emptyDataSourceMessage': '表示するレコードがありません', + 'table.pagination.firstTooltip': '最初のページ', + 'table.pagination.labelDisplayedRows': '{count} 件中 {from}-{to} 件目', + 'table.pagination.labelRowsSelect': '行', + 'table.pagination.lastTooltip': '最後のページ', + 'table.pagination.nextTooltip': '次のページ', + 'table.pagination.previousTooltip': '前のページ', + 'table.toolbar.search': 'フィルター', + 'alertDisplay.message_one': '({{ count }} 件の新しいメッセージ)', + 'alertDisplay.message_other': '({{ count }} 件の新しいメッセージ)', + 'table.header.actions': 'アクション', + 'oauthRequestDialog.message': + '{{appTitle}} が {{provider}} API と ID にアクセスすることを許可するには、サインインしてください。', + }, +}); diff --git a/packages/app-next/src/translations/rhdh/de.ts b/packages/app-next/src/translations/rhdh/de.ts new file mode 100644 index 0000000000..d2c5ae56ae --- /dev/null +++ b/packages/app-next/src/translations/rhdh/de.ts @@ -0,0 +1,113 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; + +import { rhdhTranslationRef } from './ref'; + +const rhdhTranslationDe = createTranslationMessages({ + ref: rhdhTranslationRef, + messages: { + 'menuItem.home': 'Heim', + 'menuItem.myGroup_one': 'Meine Gruppe', + 'menuItem.myGroup_other': 'Meine Gruppen', + 'menuItem.catalog': 'Katalog', + 'menuItem.apis': 'APIs', + 'menuItem.learningPaths': 'Lernpfade', + 'menuItem.selfService': 'Self-Service', + 'menuItem.userSettings': 'Benutzereinstellungen', + 'menuItem.administration': 'Verwaltung', + 'menuItem.extensions': 'Erweiterungen', + 'menuItem.clusters': 'Cluster', + 'menuItem.rbac': 'RBAC', + 'menuItem.bulkImport': 'Massenimport', + 'menuItem.docs': 'Dokumente', + 'menuItem.lighthouse': 'Leuchtturm', + 'menuItem.techRadar': 'Tech Radar', + 'menuItem.orchestrator': 'Orchestrator', + 'menuItem.adoptionInsights': 'Adoption Insights', + 'sidebar.menu': 'Menü', + 'sidebar.home': 'Heim', + 'sidebar.homeLogo': 'Startseitenlogo', + 'signIn.page.title': 'Wählen Sie eine Anmeldemethode aus', + 'signIn.providers.auth0.title': 'Auth0', + 'signIn.providers.auth0.message': 'Mit Auth0 anmelden', + 'signIn.providers.atlassian.title': 'Atlassian', + 'signIn.providers.atlassian.message': 'Mit Atlassian anmelden', + 'signIn.providers.microsoft.title': 'Microsoft', + 'signIn.providers.microsoft.message': 'Mit Microsoft anmelden', + 'signIn.providers.bitbucket.title': 'Bitbucket', + 'signIn.providers.bitbucket.message': 'Mit Bitbucket anmelden', + 'signIn.providers.bitbucketServer.title': 'Bitbucket Server', + 'signIn.providers.bitbucketServer.message': 'Mit Bitbucket Server anmelden', + 'signIn.providers.github.title': 'GitHub', + 'signIn.providers.github.message': 'Mit GitHub anmelden', + 'signIn.providers.gitlab.title': 'GitLab', + 'signIn.providers.gitlab.message': 'Mit GitLab anmelden', + 'signIn.providers.google.title': 'Google', + 'signIn.providers.google.message': 'Mit Google anmelden', + 'signIn.providers.oidc.title': 'OIDC', + 'signIn.providers.oidc.message': 'Mit OIDC anmelden', + 'signIn.providers.okta.title': 'Okta', + 'signIn.providers.okta.message': 'Mit Okta anmelden', + 'signIn.providers.onelogin.title': 'OneLogin', + 'signIn.providers.onelogin.message': 'Mit OneLogin anmelden', + 'signIn.providers.saml.title': 'SAML', + 'signIn.providers.saml.message': 'Mit SAML anmelden', + 'catalog.entityPage.overview.title': 'Übersicht', + 'catalog.entityPage.topology.title': 'Topologie', + 'catalog.entityPage.issues.title': 'Probleme', + 'catalog.entityPage.pullRequests.title': 'Pull/Merge Requests', + 'catalog.entityPage.ci.title': 'CI', + 'catalog.entityPage.cd.title': 'CD', + 'catalog.entityPage.kubernetes.title': 'Kubernetes', + 'catalog.entityPage.imageRegistry.title': 'Bildregistrierung', + 'catalog.entityPage.monitoring.title': 'Überwachung', + 'catalog.entityPage.lighthouse.title': 'Leuchtturm', + 'catalog.entityPage.api.title': 'API', + 'catalog.entityPage.dependencies.title': 'Abhängigkeiten', + 'catalog.entityPage.docs.title': 'Dokumente', + 'catalog.entityPage.definition.title': 'Definition', + 'catalog.entityPage.diagram.title': 'Systemdiagramm', + 'catalog.entityPage.workflows.title': 'Workflows', + 'app.search.title': 'Suchen', + 'app.search.resultType': 'Ergebnistyp', + 'app.search.softwareCatalog': 'Softwarekatalog', + 'app.search.filters.kind': 'Art', + 'app.search.filters.lifecycle': 'Lebenszyklus', + 'app.search.filters.component': 'Komponente', + 'app.search.filters.template': 'Vorlage', + 'app.search.filters.experimental': 'Experimental-', + 'app.search.filters.production': 'Produktion', + 'app.learningPaths.title': 'Lernpfade', + 'app.learningPaths.error.title': 'Daten konnten nicht abgerufen werden.', + 'app.learningPaths.error.unknownError': 'Unbekannter Fehler', + 'app.userSettings.infoCard.title': 'RHDH-Metadaten', + 'app.userSettings.infoCard.metadataCopied': + 'Metadaten in die Zwischenablage kopiert', + 'app.userSettings.infoCard.copyMetadata': + 'Metadaten in die Zwischenablage kopieren', + 'app.userSettings.infoCard.showLess': 'Weniger anzeigen', + 'app.userSettings.infoCard.showMore': 'Mehr anzeigen', + 'app.errors.contactSupport': 'Kontaktieren Sie den Support.', + 'app.errors.goBack': 'Zurück', + 'app.errors.notFound.message': 'Wir konnten diese Seite nicht finden.', + 'app.errors.notFound.additionalInfo': + 'Die gesuchte Seite wurde möglicherweise entfernt, umbenannt oder ist vorübergehend nicht verfügbar.', + }, +}); + +export default rhdhTranslationDe; diff --git a/packages/app-next/src/translations/rhdh/es.ts b/packages/app-next/src/translations/rhdh/es.ts new file mode 100644 index 0000000000..61ceb9ed85 --- /dev/null +++ b/packages/app-next/src/translations/rhdh/es.ts @@ -0,0 +1,114 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; + +import { rhdhTranslationRef } from './ref'; + +const rhdhTranslationEs = createTranslationMessages({ + ref: rhdhTranslationRef, + messages: { + 'menuItem.home': 'Inicio', + 'menuItem.myGroup_one': 'Mi grupo', + 'menuItem.myGroup_other': 'Mis grupos', + 'menuItem.catalog': 'Catálogo', + 'menuItem.apis': 'API', + 'menuItem.learningPaths': 'Rutas de aprendizaje', + 'menuItem.selfService': 'Autoservicio', + 'menuItem.userSettings': 'Configuración de usuario', + 'menuItem.administration': 'Administración', + 'menuItem.extensions': 'Extensiones', + 'menuItem.clusters': 'Clústeres', + 'menuItem.rbac': 'RBAC', + 'menuItem.bulkImport': 'Importación masiva', + 'menuItem.docs': 'Documentos', + 'menuItem.lighthouse': 'Lighthouse', + 'menuItem.techRadar': 'Tech Radar', + 'menuItem.orchestrator': 'Orquestador', + 'menuItem.adoptionInsights': 'Adoption Insights', + 'sidebar.menu': 'Menú', + 'sidebar.home': 'Inicio', + 'sidebar.homeLogo': 'Logotipo de inicio', + 'signIn.page.title': 'Seleccionar un método de inicio de sesión', + 'signIn.providers.auth0.title': 'Auth0', + 'signIn.providers.auth0.message': 'Iniciar sesión con Auth0', + 'signIn.providers.atlassian.title': 'Atlassian', + 'signIn.providers.atlassian.message': 'Iniciar sesión con Atlassian', + 'signIn.providers.microsoft.title': 'Microsoft', + 'signIn.providers.microsoft.message': 'Iniciar sesión con Microsoft', + 'signIn.providers.bitbucket.title': 'Bitbucket', + 'signIn.providers.bitbucket.message': 'Iniciar sesión con Bitbucket', + 'signIn.providers.bitbucketServer.title': 'Servidor de Bitbucket', + 'signIn.providers.bitbucketServer.message': + 'Iniciar sesión con el servidor de Bitbucket', + 'signIn.providers.github.title': 'GitHub', + 'signIn.providers.github.message': 'Iniciar sesión con GitHub', + 'signIn.providers.gitlab.title': 'GitLab', + 'signIn.providers.gitlab.message': 'Iniciar sesión con GitLab', + 'signIn.providers.google.title': 'Google', + 'signIn.providers.google.message': 'Iniciar sesión con Google', + 'signIn.providers.oidc.title': 'OIDC', + 'signIn.providers.oidc.message': 'Iniciar sesión con OIDC', + 'signIn.providers.okta.title': 'Okta', + 'signIn.providers.okta.message': 'Iniciar sesión con Okta', + 'signIn.providers.onelogin.title': 'OneLogin', + 'signIn.providers.onelogin.message': 'Iniciar sesión con OneLogin', + 'signIn.providers.saml.title': 'SAML', + 'signIn.providers.saml.message': 'Iniciar sesión con SAML', + 'catalog.entityPage.overview.title': 'Visión general', + 'catalog.entityPage.topology.title': 'Topología', + 'catalog.entityPage.issues.title': 'Problemas', + 'catalog.entityPage.pullRequests.title': 'Solicitudes de extracción/fusión', + 'catalog.entityPage.ci.title': 'CI', + 'catalog.entityPage.cd.title': 'CD', + 'catalog.entityPage.kubernetes.title': 'Kubernetes', + 'catalog.entityPage.imageRegistry.title': 'Registro de imágenes', + 'catalog.entityPage.monitoring.title': 'Monitoreo', + 'catalog.entityPage.lighthouse.title': 'Lighthouse', + 'catalog.entityPage.api.title': 'API', + 'catalog.entityPage.dependencies.title': 'Dependencias', + 'catalog.entityPage.docs.title': 'Documentos', + 'catalog.entityPage.definition.title': 'Definición', + 'catalog.entityPage.diagram.title': 'Diagrama del sistema', + 'catalog.entityPage.workflows.title': 'Flujos de trabajo', + 'app.search.title': 'Buscar', + 'app.search.resultType': 'Tipo de resultado', + 'app.search.softwareCatalog': 'Catálogo de software', + 'app.search.filters.kind': 'Tipo', + 'app.search.filters.lifecycle': 'Ciclo de vida', + 'app.search.filters.component': 'Componente', + 'app.search.filters.template': 'Plantilla', + 'app.search.filters.experimental': 'experimental', + 'app.search.filters.production': 'producción', + 'app.learningPaths.title': 'Rutas de aprendizaje', + 'app.learningPaths.error.title': 'No se pudieron extraer los datos.', + 'app.learningPaths.error.unknownError': 'Error desconocido', + 'app.userSettings.infoCard.title': 'Metadatos de RHDH', + 'app.userSettings.infoCard.metadataCopied': + 'Metadatos copiados en el portapapeles', + 'app.userSettings.infoCard.copyMetadata': + 'Copiar metadatos al portapapeles', + 'app.userSettings.infoCard.showLess': 'Mostrar menos', + 'app.userSettings.infoCard.showMore': 'Mostrar más', + 'app.errors.contactSupport': 'Comuníquese con Soporte', + 'app.errors.goBack': 'Volver', + 'app.errors.notFound.message': 'No pudimos encontrar esa página', + 'app.errors.notFound.additionalInfo': + 'Es posible que la página que busca se haya eliminado, haya cambiado de nombre o no esté disponible temporalmente.', + }, +}); + +export default rhdhTranslationEs; diff --git a/packages/app-next/src/translations/rhdh/fr.ts b/packages/app-next/src/translations/rhdh/fr.ts new file mode 100644 index 0000000000..5df84eb9b3 --- /dev/null +++ b/packages/app-next/src/translations/rhdh/fr.ts @@ -0,0 +1,114 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; + +import { rhdhTranslationRef } from './ref'; + +const rhdhTranslationFr = createTranslationMessages({ + ref: rhdhTranslationRef, + messages: { + 'menuItem.home': 'Accueil', + 'menuItem.myGroup_one': 'Mon groupe', + 'menuItem.myGroup_other': 'Mes groupes', + 'menuItem.catalog': 'Catalogue', + 'menuItem.apis': 'APIs', + 'menuItem.learningPaths': "Parcours d'apprentissage", + 'menuItem.selfService': 'En libre service', + 'menuItem.userSettings': 'Paramètres utilisateur', + 'menuItem.administration': 'Administration', + 'menuItem.extensions': 'Extensions', + 'menuItem.clusters': 'Clusters', + 'menuItem.rbac': 'RBAC', + 'menuItem.bulkImport': 'Importation en vrac', + 'menuItem.docs': 'Docs', + 'menuItem.lighthouse': 'Phare', + 'menuItem.techRadar': 'Radar technologique', + 'menuItem.orchestrator': 'Orchestrateur', + 'menuItem.adoptionInsights': "Perspectives sur l'adoption", + 'sidebar.menu': 'Menu', + 'sidebar.home': 'Accueil', + 'sidebar.homeLogo': "Logo d'accueil", + 'signIn.page.title': 'Choisissez une méthode de connexion', + 'signIn.providers.auth0.title': 'Auth0', + 'signIn.providers.auth0.message': 'Connectez-vous avec Auth0', + 'signIn.providers.atlassian.title': 'Atlassian', + 'signIn.providers.atlassian.message': 'Connectez-vous avec Atlassian', + 'signIn.providers.microsoft.title': 'Microsoft', + 'signIn.providers.microsoft.message': 'Connectez-vous avec Microsoft', + 'signIn.providers.bitbucket.title': 'BitBucket', + 'signIn.providers.bitbucket.message': 'Se connecter avec Bitbucket', + 'signIn.providers.bitbucketServer.title': 'Serveur Bitbucket', + 'signIn.providers.bitbucketServer.message': + 'Se connecter via le serveur Bitbucket', + 'signIn.providers.github.title': 'GitHub', + 'signIn.providers.github.message': 'Se connecter via GitHub', + 'signIn.providers.gitlab.title': 'GitLab', + 'signIn.providers.gitlab.message': 'Connectez-vous avec GitLab', + 'signIn.providers.google.title': 'Google', + 'signIn.providers.google.message': 'Se connecter avec Google', + 'signIn.providers.oidc.title': 'OIDC', + 'signIn.providers.oidc.message': "Connectez-vous à l'aide d'OIDC", + 'signIn.providers.okta.title': 'Okta', + 'signIn.providers.okta.message': 'Connectez-vous via Okta', + 'signIn.providers.onelogin.title': 'OneLogin', + 'signIn.providers.onelogin.message': 'Se connecter avec OneLogin', + 'signIn.providers.saml.title': 'SAML', + 'signIn.providers.saml.message': 'Se connecter via SAML', + 'catalog.entityPage.overview.title': 'Vue d’ensemble', + 'catalog.entityPage.topology.title': 'Topologie', + 'catalog.entityPage.issues.title': 'Problèmes', + 'catalog.entityPage.pullRequests.title': 'Demandes de fusion/extraction', + 'catalog.entityPage.ci.title': 'CI', + 'catalog.entityPage.cd.title': 'CD', + 'catalog.entityPage.kubernetes.title': 'Kubernetes', + 'catalog.entityPage.imageRegistry.title': 'Registre d’images', + 'catalog.entityPage.monitoring.title': 'Surveillance', + 'catalog.entityPage.lighthouse.title': 'Phare', + 'catalog.entityPage.api.title': 'API', + 'catalog.entityPage.dependencies.title': 'Dépendances', + 'catalog.entityPage.docs.title': 'Docs', + 'catalog.entityPage.definition.title': 'Définition', + 'catalog.entityPage.diagram.title': 'Diagramme du système', + 'catalog.entityPage.workflows.title': 'Flux de travail', + 'app.search.title': 'Recherche', + 'app.search.resultType': 'Type de résultat', + 'app.search.softwareCatalog': 'Catalogue de logiciels', + 'app.search.filters.kind': 'Type', + 'app.search.filters.lifecycle': 'Cycle de vie', + 'app.search.filters.component': 'Composant', + 'app.search.filters.template': 'Modèle', + 'app.search.filters.experimental': 'expérimental', + 'app.search.filters.production': 'production', + 'app.learningPaths.title': "Parcours d'apprentissage", + 'app.learningPaths.error.title': 'Impossible de récupérer les données', + 'app.learningPaths.error.unknownError': 'Erreur inconnue', + 'app.userSettings.infoCard.title': 'Métadonnées RHDH', + 'app.userSettings.infoCard.metadataCopied': + 'Métadonnées copiées dans le presse-papiers', + 'app.userSettings.infoCard.copyMetadata': + 'Copiez les métadonnées dans votre presse-papiers', + 'app.userSettings.infoCard.showLess': 'Montrer moins', + 'app.userSettings.infoCard.showMore': 'Afficher davantage', + 'app.errors.contactSupport': "Contactez l'assistance", + 'app.errors.goBack': 'Retour', + 'app.errors.notFound.message': "Nous n'avons pas pu trouver cette page.", + 'app.errors.notFound.additionalInfo': + 'La page que vous recherchez a peut-être été supprimée, renommée ou est temporairement indisponible.', + }, +}); + +export default rhdhTranslationFr; diff --git a/packages/app-next/src/translations/rhdh/index.ts b/packages/app-next/src/translations/rhdh/index.ts new file mode 100644 index 0000000000..a119bfa77f --- /dev/null +++ b/packages/app-next/src/translations/rhdh/index.ts @@ -0,0 +1,35 @@ +/* + * 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 { createTranslationResource } from '@backstage/core-plugin-api/alpha'; + +import { rhdhTranslationRef } from './ref'; + +/** + * Translation Resource for RHDH Application + */ +export const rhdhTranslations = createTranslationResource({ + ref: rhdhTranslationRef, + translations: { + de: () => import('./de') as any, + es: () => import('./es') as any, + fr: () => import('./fr') as any, + it: () => import('./it') as any, + ja: () => import('./ja') as any, + }, +}); + +export { rhdhTranslationRef }; diff --git a/packages/app-next/src/translations/rhdh/it.ts b/packages/app-next/src/translations/rhdh/it.ts new file mode 100644 index 0000000000..fe240b20bf --- /dev/null +++ b/packages/app-next/src/translations/rhdh/it.ts @@ -0,0 +1,112 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; + +import { rhdhTranslationRef } from './ref'; + +const rhdhTranslationIt = createTranslationMessages({ + ref: rhdhTranslationRef, + messages: { + 'menuItem.home': 'Casa', + 'menuItem.myGroup_one': 'Il mio gruppo', + 'menuItem.myGroup_other': 'I miei gruppi', + 'menuItem.catalog': 'Catalogo', + 'menuItem.apis': 'API', + 'menuItem.learningPaths': 'Learning Path', + 'menuItem.selfService': 'Self service', + 'menuItem.userSettings': 'Impostazioni utente', + 'menuItem.administration': 'Amministrazione', + 'menuItem.extensions': 'Estensioni', + 'menuItem.clusters': 'Cluster', + 'menuItem.rbac': 'RBAC', + 'menuItem.bulkImport': 'Importazione in blocco', + 'menuItem.docs': 'Documenti', + 'menuItem.lighthouse': 'Lighthouse', + 'menuItem.techRadar': 'Tech Radar', + 'menuItem.orchestrator': 'Orchestratore', + 'menuItem.adoptionInsights': 'Insights adozione', + 'sidebar.menu': 'Menu', + 'sidebar.home': 'Casa', + 'sidebar.homeLogo': 'Logo casa', + 'signIn.page.title': 'Seleziona un metodo di accesso', + 'signIn.providers.auth0.title': 'Auth0', + 'signIn.providers.auth0.message': 'Accedi con Auth0', + 'signIn.providers.atlassian.title': 'Atlassian', + 'signIn.providers.atlassian.message': 'Accedi con Atlassian', + 'signIn.providers.microsoft.title': 'Microsoft', + 'signIn.providers.microsoft.message': 'Accedi con Microsoft', + 'signIn.providers.bitbucket.title': 'Bitbucket', + 'signIn.providers.bitbucket.message': 'Accedi con Bitbucket', + 'signIn.providers.bitbucketServer.title': 'Bitbucket Server', + 'signIn.providers.bitbucketServer.message': 'Accedi con Bitbucket Server', + 'signIn.providers.github.title': 'GitHub', + 'signIn.providers.github.message': 'Accedi con GitHub', + 'signIn.providers.gitlab.title': 'GitLab', + 'signIn.providers.gitlab.message': 'Accedi con GitLab', + 'signIn.providers.google.title': 'Google', + 'signIn.providers.google.message': 'Accedi con Google', + 'signIn.providers.oidc.title': 'OIDC', + 'signIn.providers.oidc.message': 'Accedi con OIDC', + 'signIn.providers.okta.title': 'Okta', + 'signIn.providers.okta.message': 'Accedi con Okta', + 'signIn.providers.onelogin.title': 'OneLogin', + 'signIn.providers.onelogin.message': 'Accedi con OneLogin', + 'signIn.providers.saml.title': 'SAML', + 'signIn.providers.saml.message': 'Accedi con SAML', + 'catalog.entityPage.overview.title': 'Panoramica', + 'catalog.entityPage.topology.title': 'Topologia', + 'catalog.entityPage.issues.title': 'Problemi', + 'catalog.entityPage.pullRequests.title': 'Pull/Merge Request', + 'catalog.entityPage.ci.title': 'CI', + 'catalog.entityPage.cd.title': 'CD', + 'catalog.entityPage.kubernetes.title': 'Kubernetes', + 'catalog.entityPage.imageRegistry.title': 'Registro immagine', + 'catalog.entityPage.monitoring.title': 'Monitoraggio', + 'catalog.entityPage.lighthouse.title': 'Lighthouse', + 'catalog.entityPage.api.title': 'API', + 'catalog.entityPage.dependencies.title': 'Dipendenze', + 'catalog.entityPage.docs.title': 'Documenti', + 'catalog.entityPage.definition.title': 'Definizione', + 'catalog.entityPage.diagram.title': 'Diagramma di sistema', + 'catalog.entityPage.workflows.title': 'Flussi di lavoro', + 'app.search.title': 'Ricerca', + 'app.search.resultType': 'Tipo di risultato', + 'app.search.softwareCatalog': 'Catalogo software', + 'app.search.filters.kind': 'Tipo', + 'app.search.filters.lifecycle': 'Ciclo di vita', + 'app.search.filters.component': 'Componente', + 'app.search.filters.template': 'Modello', + 'app.search.filters.experimental': 'sperimentale', + 'app.search.filters.production': 'produzione', + 'app.learningPaths.title': 'Learning Path', + 'app.learningPaths.error.title': 'Impossibile estrarre i dati.', + 'app.learningPaths.error.unknownError': 'Errore sconosciuto', + 'app.userSettings.infoCard.title': 'Metadati RHDH', + 'app.userSettings.infoCard.metadataCopied': + 'Metadati copiati negli appunti', + 'app.userSettings.infoCard.copyMetadata': 'Copia i metadati negli appunti', + 'app.userSettings.infoCard.showLess': 'Mostra meno', + 'app.userSettings.infoCard.showMore': 'Mostra altro', + 'app.errors.contactSupport': 'Contatta il supporto', + 'app.errors.goBack': 'Torna indietro', + 'app.errors.notFound.message': 'Non siamo riusciti a trovare quella pagina', + 'app.errors.notFound.additionalInfo': + 'La pagina che stai cercando potrebbe essere stata rimossa, aver cambiato nome o essere temporaneamente non disponibile.', + }, +}); + +export default rhdhTranslationIt; diff --git a/packages/app-next/src/translations/rhdh/ja.ts b/packages/app-next/src/translations/rhdh/ja.ts new file mode 100644 index 0000000000..d4cac78a51 --- /dev/null +++ b/packages/app-next/src/translations/rhdh/ja.ts @@ -0,0 +1,114 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; + +import { rhdhTranslationRef } from './ref'; + +const rhdhTranslationJa = createTranslationMessages({ + ref: rhdhTranslationRef, + messages: { + 'menuItem.home': 'ホーム', + 'menuItem.myGroup_one': 'マイグループ', + 'menuItem.myGroup_other': 'マイグループ', + 'menuItem.catalog': 'カタログ', + 'menuItem.apis': 'API', + 'menuItem.learningPaths': 'ラーニングパス', + 'menuItem.selfService': 'セルフサービス', + 'menuItem.userSettings': 'ユーザー設定', + 'menuItem.administration': '管理', + 'menuItem.extensions': '拡張機能', + 'menuItem.clusters': 'クラスター', + 'menuItem.rbac': 'RBAC', + 'menuItem.bulkImport': '一括インポート', + 'menuItem.docs': 'ドキュメント', + 'menuItem.lighthouse': 'Lighthouse', + 'menuItem.techRadar': 'Tech Radar', + 'menuItem.orchestrator': 'オーケストレーター', + 'menuItem.adoptionInsights': 'Adoption Insights', + 'sidebar.menu': 'メニュー', + 'sidebar.home': 'ホーム', + 'sidebar.homeLogo': 'ホームロゴ', + 'signIn.page.title': 'サインイン方法の選択', + 'signIn.providers.auth0.title': 'Auth0', + 'signIn.providers.auth0.message': 'Auth0 を使用してサインイン', + 'signIn.providers.atlassian.title': 'Atlassian', + 'signIn.providers.atlassian.message': 'Atlassian を使用してサインイン', + 'signIn.providers.microsoft.title': 'Microsoft', + 'signIn.providers.microsoft.message': 'Microsoft を使用してサインイン', + 'signIn.providers.bitbucket.title': 'Bitbucket', + 'signIn.providers.bitbucket.message': 'Bitbucket を使用してサインイン', + 'signIn.providers.bitbucketServer.title': 'Bitbucket Server', + 'signIn.providers.bitbucketServer.message': + 'Bitbucket Server を使用してサインイン', + 'signIn.providers.github.title': 'GitHub', + 'signIn.providers.github.message': 'GitHub を使用してサインイン', + 'signIn.providers.gitlab.title': 'GitLab', + 'signIn.providers.gitlab.message': 'GitLab を使用してサインイン', + 'signIn.providers.google.title': 'Google', + 'signIn.providers.google.message': 'Google を使用してサインイン', + 'signIn.providers.oidc.title': 'OIDC', + 'signIn.providers.oidc.message': 'OIDC を使用してサインイン', + 'signIn.providers.okta.title': 'Okta', + 'signIn.providers.okta.message': 'Okta を使用してサインイン', + 'signIn.providers.onelogin.title': 'OneLogin', + 'signIn.providers.onelogin.message': 'OneLogin を使用してサインイン', + 'signIn.providers.saml.title': 'SAML', + 'signIn.providers.saml.message': 'SAML を使用してサインイン', + 'catalog.entityPage.overview.title': '概要', + 'catalog.entityPage.topology.title': 'トポロジー', + 'catalog.entityPage.issues.title': 'イシュー', + 'catalog.entityPage.pullRequests.title': 'プル/マージリクエスト', + 'catalog.entityPage.ci.title': 'CI', + 'catalog.entityPage.cd.title': 'CD', + 'catalog.entityPage.kubernetes.title': 'Kubernetes', + 'catalog.entityPage.imageRegistry.title': 'イメージレジストリー', + 'catalog.entityPage.monitoring.title': 'モニタリング', + 'catalog.entityPage.lighthouse.title': 'Lighthouse', + 'catalog.entityPage.api.title': 'API', + 'catalog.entityPage.dependencies.title': '依存関係', + 'catalog.entityPage.docs.title': 'ドキュメント', + 'catalog.entityPage.definition.title': '定義', + 'catalog.entityPage.diagram.title': 'システム図', + 'catalog.entityPage.workflows.title': 'ワークフロー', + 'app.search.title': '検索', + 'app.search.resultType': '結果タイプ', + 'app.search.softwareCatalog': 'ソフトウェアカタログ', + 'app.search.filters.kind': '種類', + 'app.search.filters.lifecycle': 'ライフサイクル', + 'app.search.filters.component': 'コンポーネント', + 'app.search.filters.template': 'テンプレート', + 'app.search.filters.experimental': '実験的', + 'app.search.filters.production': '実稼働', + 'app.learningPaths.title': 'ラーニングパス', + 'app.learningPaths.error.title': 'データを取得できませんでした。', + 'app.learningPaths.error.unknownError': '不明なエラー', + 'app.userSettings.infoCard.title': 'RHDH メタデータ', + 'app.userSettings.infoCard.metadataCopied': + 'メタデータがクリップボードにコピーされました', + 'app.userSettings.infoCard.copyMetadata': + 'メタデータをクリップボードにコピーする', + 'app.userSettings.infoCard.showLess': '簡易表示', + 'app.userSettings.infoCard.showMore': '詳細表示', + 'app.errors.contactSupport': 'サポートにお問い合わせください', + 'app.errors.goBack': '戻る', + 'app.errors.notFound.message': '該当するページが見つかりませんでした', + 'app.errors.notFound.additionalInfo': + 'お探しのページは削除されたか、名前が変更されたか、一時的に利用できない可能性があります。', + }, +}); + +export default rhdhTranslationJa; diff --git a/packages/app-next/src/translations/rhdh/ref.ts b/packages/app-next/src/translations/rhdh/ref.ts new file mode 100644 index 0000000000..ae3267c652 --- /dev/null +++ b/packages/app-next/src/translations/rhdh/ref.ts @@ -0,0 +1,211 @@ +/* + * 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 { createTranslationRef } from '@backstage/core-plugin-api/alpha'; + +/** + * Messages object containing all English translations. + * This is our single source of truth for translations. + */ +export const rhdhMessages = { + menuItem: { + // Default main menu items from consts.ts + home: 'Home', + myGroup_one: 'My Group', + myGroup_other: 'My Groups', + catalog: 'Catalog', + apis: 'APIs', + learningPaths: 'Learning Paths', + selfService: 'Self-service', + userSettings: 'User Settings', + administration: 'Administration', + extensions: 'Extensions', + + // dynamic-plugins.default.main-menu-items + clusters: 'Clusters', + rbac: 'RBAC', + bulkImport: 'Bulk import', + docs: 'Docs', + lighthouse: 'Lighthouse', + techRadar: 'Tech Radar', + orchestrator: 'Orchestrator', + adoptionInsights: 'Adoption Insights', + }, + sidebar: { + menu: 'Menu', + home: 'Home', + homeLogo: 'Home logo', + }, + signIn: { + page: { + title: 'Select a sign-in method', + }, + providers: { + auth0: { + title: 'Auth0', + message: 'Sign in using Auth0', + }, + atlassian: { + title: 'Atlassian', + message: 'Sign in using Atlassian', + }, + microsoft: { + title: 'Microsoft', + message: 'Sign in using Microsoft', + }, + bitbucket: { + title: 'Bitbucket', + message: 'Sign in using Bitbucket', + }, + bitbucketServer: { + title: 'Bitbucket Server', + message: 'Sign in using Bitbucket Server', + }, + github: { + title: 'GitHub', + message: 'Sign in using GitHub', + }, + gitlab: { + title: 'GitLab', + message: 'Sign in using GitLab', + }, + google: { + title: 'Google', + message: 'Sign in using Google', + }, + oidc: { + title: 'OIDC', + message: 'Sign in using OIDC', + }, + okta: { + title: 'Okta', + message: 'Sign in using Okta', + }, + onelogin: { + title: 'OneLogin', + message: 'Sign in using OneLogin', + }, + saml: { + title: 'SAML', + message: 'Sign in using SAML', + }, + }, + }, + catalog: { + entityPage: { + overview: { + title: 'Overview', + }, + topology: { + title: 'Topology', + }, + issues: { + title: 'Issues', + }, + pullRequests: { + title: 'Pull/Merge Requests', + }, + ci: { + title: 'CI', + }, + cd: { + title: 'CD', + }, + kubernetes: { + title: 'Kubernetes', + }, + imageRegistry: { + title: 'Image Registry', + }, + monitoring: { + title: 'Monitoring', + }, + lighthouse: { + title: 'Lighthouse', + }, + api: { + title: 'API', + }, + dependencies: { + title: 'Dependencies', + }, + docs: { + title: 'Docs', + }, + definition: { + title: 'Definition', + }, + diagram: { + title: 'System Diagram', + }, + workflows: { + title: 'Workflows', + }, + }, + }, + app: { + search: { + title: 'Search', + resultType: 'Result Type', + softwareCatalog: 'Software Catalog', + filters: { + kind: 'Kind', + lifecycle: 'Lifecycle', + component: 'Component', + template: 'Template', + experimental: 'experimental', + production: 'production', + }, + }, + learningPaths: { + title: 'Learning Paths', + error: { + title: 'Could not fetch data.', + unknownError: 'Unknown error', + }, + }, + userSettings: { + infoCard: { + title: 'RHDH Metadata', + metadataCopied: 'Metadata copied to clipboard', + copyMetadata: 'Copy metadata to your clipboard', + showLess: 'Show less', + showMore: 'Show more', + }, + }, + errors: { + contactSupport: 'Contact support', + goBack: 'Go back', + notFound: { + message: "We couldn't find that page", + additionalInfo: + 'The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.', + }, + }, + table: { + createdAt: 'Created At', + }, + }, +}; + +/** + * Translation reference for Quickstart plugin + * @public + */ +export const rhdhTranslationRef = createTranslationRef({ + id: 'rhdh', + messages: rhdhMessages, +}); diff --git a/packages/app-next/src/translations/scaffolder/de.ts b/packages/app-next/src/translations/scaffolder/de.ts new file mode 100644 index 0000000000..edbee41cce --- /dev/null +++ b/packages/app-next/src/translations/scaffolder/de.ts @@ -0,0 +1,33 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '@backstage/plugin-scaffolder'; + +const de = createTranslationMessages({ + ref: scaffolderTranslationRef, + full: false, + messages: { + 'templateListPage.title': 'Self-Service', + 'templateListPage.pageTitle': 'Self-Service', + 'templateWizardPage.title': 'Self-Service', + 'templateWizardPage.pageTitle': 'Self-Service', + 'templateListPage.contentHeader.registerExistingButtonTitle': + 'Vorhandenes Git-Repository importieren', + }, +}); + +export default de; diff --git a/packages/app-next/src/translations/scaffolder/es.ts b/packages/app-next/src/translations/scaffolder/es.ts new file mode 100644 index 0000000000..acd772cd3d --- /dev/null +++ b/packages/app-next/src/translations/scaffolder/es.ts @@ -0,0 +1,33 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '@backstage/plugin-scaffolder'; + +const es = createTranslationMessages({ + ref: scaffolderTranslationRef, + full: false, + messages: { + 'templateListPage.title': 'Autoservicio', + 'templateListPage.pageTitle': 'Autoservicio', + 'templateWizardPage.title': 'Autoservicio', + 'templateWizardPage.pageTitle': 'Autoservicio', + 'templateListPage.contentHeader.registerExistingButtonTitle': + 'Importar un repositorio Git existente', + }, +}); + +export default es; diff --git a/packages/app-next/src/translations/scaffolder/fr.ts b/packages/app-next/src/translations/scaffolder/fr.ts new file mode 100644 index 0000000000..631c36994f --- /dev/null +++ b/packages/app-next/src/translations/scaffolder/fr.ts @@ -0,0 +1,31 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '@backstage/plugin-scaffolder'; + +export default createTranslationMessages({ + ref: scaffolderTranslationRef, + full: false, + messages: { + 'templateListPage.title': 'Self-service', + 'templateListPage.pageTitle': 'Self-service', + 'templateWizardPage.title': 'Self-service', + 'templateWizardPage.pageTitle': 'Self-service', + 'templateListPage.contentHeader.registerExistingButtonTitle': + 'Importer un dépôt Git existant', + }, +}); diff --git a/packages/app-next/src/translations/scaffolder/it.ts b/packages/app-next/src/translations/scaffolder/it.ts new file mode 100644 index 0000000000..82fc4fd475 --- /dev/null +++ b/packages/app-next/src/translations/scaffolder/it.ts @@ -0,0 +1,31 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '@backstage/plugin-scaffolder'; + +export default createTranslationMessages({ + ref: scaffolderTranslationRef, + full: false, + messages: { + 'templateListPage.title': 'Self-service', + 'templateListPage.pageTitle': 'Self-service', + 'templateWizardPage.title': 'Self-service', + 'templateWizardPage.pageTitle': 'Self-service', + 'templateListPage.contentHeader.registerExistingButtonTitle': + 'Importa un repository Git esistente', + }, +}); diff --git a/packages/app-next/src/translations/scaffolder/ja.ts b/packages/app-next/src/translations/scaffolder/ja.ts new file mode 100644 index 0000000000..edba357d5c --- /dev/null +++ b/packages/app-next/src/translations/scaffolder/ja.ts @@ -0,0 +1,31 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '@backstage/plugin-scaffolder'; + +export default createTranslationMessages({ + ref: scaffolderTranslationRef, + full: false, + messages: { + 'templateListPage.title': 'セルフサービス', + 'templateListPage.pageTitle': 'セルフサービス', + 'templateWizardPage.title': 'セルフサービス', + 'templateWizardPage.pageTitle': 'セルフサービス', + 'templateListPage.contentHeader.registerExistingButtonTitle': + '既存の Git リポジトリーのインポート', + }, +}); diff --git a/packages/app-next/src/translations/scaffolder/scaffolder-en.ts b/packages/app-next/src/translations/scaffolder/scaffolder-en.ts new file mode 100644 index 0000000000..5a0466c2b9 --- /dev/null +++ b/packages/app-next/src/translations/scaffolder/scaffolder-en.ts @@ -0,0 +1,17 @@ +import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '@backstage/plugin-scaffolder'; + +const en = createTranslationMessages({ + ref: scaffolderTranslationRef, + full: false, // False means that this is a partial translation + messages: { + 'templateListPage.title': 'Self-service', + 'templateListPage.pageTitle': 'Self-service', + 'templateWizardPage.title': 'Self-service', + 'templateWizardPage.pageTitle': 'Self-service', + 'templateListPage.contentHeader.registerExistingButtonTitle': + 'Import an existing Git repository', + }, +}); + +export default en; diff --git a/packages/app-next/src/translations/scaffolder/scaffolder.ts b/packages/app-next/src/translations/scaffolder/scaffolder.ts new file mode 100644 index 0000000000..fc089562a3 --- /dev/null +++ b/packages/app-next/src/translations/scaffolder/scaffolder.ts @@ -0,0 +1,14 @@ +import { createTranslationResource } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '@backstage/plugin-scaffolder'; + +export const scaffolderTranslations = createTranslationResource({ + ref: scaffolderTranslationRef, + translations: { + de: () => import('./de'), + en: () => import('./scaffolder-en'), + es: () => import('./es'), + fr: () => import('./fr'), + it: () => import('./it'), + ja: () => import('./ja'), + }, +}); diff --git a/packages/app-next/src/translations/search/ja.ts b/packages/app-next/src/translations/search/ja.ts new file mode 100644 index 0000000000..8b9678f706 --- /dev/null +++ b/packages/app-next/src/translations/search/ja.ts @@ -0,0 +1,26 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { searchTranslationRef } from '@backstage/plugin-search'; + +export default createTranslationMessages({ + ref: searchTranslationRef, + full: false, + messages: { + 'sidebarSearchModal.title': '検索', + }, +}); diff --git a/packages/app-next/src/translations/search/search-de.ts b/packages/app-next/src/translations/search/search-de.ts new file mode 100644 index 0000000000..139f249ae4 --- /dev/null +++ b/packages/app-next/src/translations/search/search-de.ts @@ -0,0 +1,28 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { searchTranslationRef } from '@backstage/plugin-search'; + +const de = createTranslationMessages({ + ref: searchTranslationRef, + full: false, + messages: { + 'sidebarSearchModal.title': 'Suchen', + }, +}); + +export default de; diff --git a/packages/app-next/src/translations/search/search-en.ts b/packages/app-next/src/translations/search/search-en.ts new file mode 100644 index 0000000000..f71119835c --- /dev/null +++ b/packages/app-next/src/translations/search/search-en.ts @@ -0,0 +1,12 @@ +import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { searchTranslationRef } from '@backstage/plugin-search'; + +const en = createTranslationMessages({ + ref: searchTranslationRef, + full: false, + messages: { + 'sidebarSearchModal.title': 'Search', + }, +}); + +export default en; diff --git a/packages/app-next/src/translations/search/search-es.ts b/packages/app-next/src/translations/search/search-es.ts new file mode 100644 index 0000000000..125bb7f43a --- /dev/null +++ b/packages/app-next/src/translations/search/search-es.ts @@ -0,0 +1,28 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { searchTranslationRef } from '@backstage/plugin-search'; + +const es = createTranslationMessages({ + ref: searchTranslationRef, + full: false, + messages: { + 'sidebarSearchModal.title': 'Buscar', + }, +}); + +export default es; diff --git a/packages/app-next/src/translations/search/search-fr.ts b/packages/app-next/src/translations/search/search-fr.ts new file mode 100644 index 0000000000..6350ca37b3 --- /dev/null +++ b/packages/app-next/src/translations/search/search-fr.ts @@ -0,0 +1,32 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { searchTranslationRef } from '@backstage/plugin-search'; + +export default createTranslationMessages({ + ref: searchTranslationRef, + full: false, + messages: { + 'sidebarSearchModal.title': 'Rechercher', + 'searchModal.viewFullResults': 'Voir les résultats complets', + 'searchType.allResults': 'Tous les résultats', + 'searchType.tabs.allTitle': 'Tous', + 'searchType.accordion.allTitle': 'Tous', + 'searchType.accordion.collapse': 'Effondrement', + 'searchType.accordion.numberOfResults': '{{number}} résultats', + }, +}); diff --git a/packages/app-next/src/translations/search/search-it.ts b/packages/app-next/src/translations/search/search-it.ts new file mode 100644 index 0000000000..ce8cf64576 --- /dev/null +++ b/packages/app-next/src/translations/search/search-it.ts @@ -0,0 +1,26 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { searchTranslationRef } from '@backstage/plugin-search'; + +export default createTranslationMessages({ + ref: searchTranslationRef, + full: false, + messages: { + 'sidebarSearchModal.title': 'Ricerca', + }, +}); diff --git a/packages/app-next/src/translations/search/search.ts b/packages/app-next/src/translations/search/search.ts new file mode 100644 index 0000000000..36fe5582a8 --- /dev/null +++ b/packages/app-next/src/translations/search/search.ts @@ -0,0 +1,14 @@ +import { createTranslationResource } from '@backstage/core-plugin-api/alpha'; +import { searchTranslationRef } from '@backstage/plugin-search'; + +export const searchTranslations = createTranslationResource({ + ref: searchTranslationRef, + translations: { + de: () => import('./search-de'), + en: () => import('./search-en'), + es: () => import('./search-es'), + fr: () => import('./search-fr'), + it: () => import('./search-it'), + ja: () => import('./ja'), + }, +}); diff --git a/packages/app-next/src/translations/translationsModule.ts b/packages/app-next/src/translations/translationsModule.ts new file mode 100644 index 0000000000..783b3a6d3a --- /dev/null +++ b/packages/app-next/src/translations/translationsModule.ts @@ -0,0 +1,50 @@ +import { createFrontendModule } from '@backstage/frontend-plugin-api'; +import { TranslationBlueprint } from '@backstage/plugin-app-react'; + +import { catalogTranslations } from './catalog/catalog'; +import { catalogImportTranslations } from './catalog-import/catalog-import'; +import { coreComponentsTranslations } from './core-components/core-components'; +import { rhdhTranslations } from './rhdh'; +import { scaffolderTranslations } from './scaffolder/scaffolder'; +import { searchTranslations } from './search/search'; +import { userSettingsTranslations } from './user-settings/user-settings'; + +/** + * RHDH core translations (`rhdhTranslationRef`, id `rhdh`) plus overrides for + * the catalog, catalog-import, scaffolder, search, user-settings, and + * core-components plugin translation resources — ported from + * `packages/app/src/translations`. Locales: de, es, fr, it, ja. + */ +export const rhdhTranslationsModule = createFrontendModule({ + pluginId: 'app', + extensions: [ + TranslationBlueprint.make({ + name: 'rhdh', + params: { resource: rhdhTranslations }, + }), + TranslationBlueprint.make({ + name: 'catalog', + params: { resource: catalogTranslations }, + }), + TranslationBlueprint.make({ + name: 'catalog-import', + params: { resource: catalogImportTranslations }, + }), + TranslationBlueprint.make({ + name: 'scaffolder', + params: { resource: scaffolderTranslations }, + }), + TranslationBlueprint.make({ + name: 'search', + params: { resource: searchTranslations }, + }), + TranslationBlueprint.make({ + name: 'user-settings', + params: { resource: userSettingsTranslations }, + }), + TranslationBlueprint.make({ + name: 'core-components', + params: { resource: coreComponentsTranslations }, + }), + ], +}); diff --git a/packages/app-next/src/translations/user-settings/ja.ts b/packages/app-next/src/translations/user-settings/ja.ts new file mode 100644 index 0000000000..a994ae8e85 --- /dev/null +++ b/packages/app-next/src/translations/user-settings/ja.ts @@ -0,0 +1,26 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { userSettingsTranslationRef } from '@backstage/plugin-user-settings'; + +export default createTranslationMessages({ + ref: userSettingsTranslationRef, + full: false, + messages: { + sidebarTitle: '設定', + }, +}); diff --git a/packages/app-next/src/translations/user-settings/user-settings-de.ts b/packages/app-next/src/translations/user-settings/user-settings-de.ts new file mode 100644 index 0000000000..994f36fdb9 --- /dev/null +++ b/packages/app-next/src/translations/user-settings/user-settings-de.ts @@ -0,0 +1,28 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { userSettingsTranslationRef } from '@backstage/plugin-user-settings'; + +const de = createTranslationMessages({ + ref: userSettingsTranslationRef, + full: false, + messages: { + sidebarTitle: 'Einstellungen', + }, +}); + +export default de; diff --git a/packages/app-next/src/translations/user-settings/user-settings-en.ts b/packages/app-next/src/translations/user-settings/user-settings-en.ts new file mode 100644 index 0000000000..cbbdf7d919 --- /dev/null +++ b/packages/app-next/src/translations/user-settings/user-settings-en.ts @@ -0,0 +1,12 @@ +import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { userSettingsTranslationRef } from '@backstage/plugin-user-settings'; + +const en = createTranslationMessages({ + ref: userSettingsTranslationRef, + full: false, + messages: { + sidebarTitle: 'Settings', + }, +}); + +export default en; diff --git a/packages/app-next/src/translations/user-settings/user-settings-es.ts b/packages/app-next/src/translations/user-settings/user-settings-es.ts new file mode 100644 index 0000000000..b653fc92bb --- /dev/null +++ b/packages/app-next/src/translations/user-settings/user-settings-es.ts @@ -0,0 +1,28 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { userSettingsTranslationRef } from '@backstage/plugin-user-settings'; + +const es = createTranslationMessages({ + ref: userSettingsTranslationRef, + full: false, + messages: { + sidebarTitle: 'Configuración', + }, +}); + +export default es; diff --git a/packages/app-next/src/translations/user-settings/user-settings-fr.ts b/packages/app-next/src/translations/user-settings/user-settings-fr.ts new file mode 100644 index 0000000000..c78ed9bf2f --- /dev/null +++ b/packages/app-next/src/translations/user-settings/user-settings-fr.ts @@ -0,0 +1,84 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { userSettingsTranslationRef } from '@backstage/plugin-user-settings'; + +export default createTranslationMessages({ + ref: userSettingsTranslationRef, + full: false, + messages: { + sidebarTitle: 'Paramètres', + 'languageToggle.title': 'Langue', + 'languageToggle.description': 'Changer la langue', + 'languageToggle.select': 'Sélectionnez la langue {{language}}', + 'themeToggle.title': 'Thème', + 'themeToggle.description': 'Changer le mode thème', + 'themeToggle.select': 'Sélectionnez {{theme}}', + 'themeToggle.selectAuto': 'Sélectionner le thème automatique', + 'themeToggle.names.light': 'Clair', + 'themeToggle.names.dark': 'Sombre', + 'themeToggle.names.auto': 'Auto', + 'signOutMenu.title': 'Se déconnecter', + 'signOutMenu.moreIconTitle': 'davantage', + 'pinToggle.title': 'Épingler la barre latérale', + 'pinToggle.description': 'Empêcher la fermeture de la barre latérale', + 'pinToggle.switchTitles.unpin': 'Détacher la barre latérale', + 'pinToggle.switchTitles.pin': 'Épingler la barre latérale', + 'pinToggle.ariaLabelTitle': 'Commutateur de barre latérale à broches', + 'identityCard.title': 'Identité en coulisses', + 'identityCard.noIdentityTitle': 'Aucune identité en coulisses', + 'identityCard.userEntity': 'Entité utilisateur', + 'identityCard.ownershipEntities': 'Entités de propriété', + 'defaultProviderSettings.description': + 'Fournit une authentification auprès des API et des identités de {{provider}}', + 'emptyProviders.title': "Aucun fournisseur d'authentification", + 'emptyProviders.description': + "Vous pouvez ajouter des fournisseurs d'authentification à Backstage, ce qui vous permet de les utiliser pour vous authentifier.", + 'emptyProviders.action.title': + 'Ouvrez le fichier app-config.yaml et effectuez les modifications indiquées ci-dessous :', + 'emptyProviders.action.readMoreButtonTitle': 'En savoir plus', + 'providerSettingsItem.title.signIn': 'Connectez-vous à {{title}}', + 'providerSettingsItem.title.signOut': 'Déconnexion de {{title}}', + 'providerSettingsItem.buttonTitle.signIn': 'Se connecter', + 'providerSettingsItem.buttonTitle.signOut': 'se déconnecter', + 'authProviders.title': 'Fournisseurs disponibles', + 'defaultSettingsPage.tabsTitle.general': 'Général', + 'defaultSettingsPage.tabsTitle.authProviders': + "Fournisseurs d'authentification", + 'defaultSettingsPage.tabsTitle.featureFlags': 'Drapeaux de fonctionnalités', + 'featureFlags.title': 'Drapeaux de fonctionnalités', + 'featureFlags.description': + 'Veuillez actualiser la page lorsque vous modifiez les options de personnalisation.', + 'featureFlags.emptyFlags.title': 'Aucun indicateur de fonctionnalité', + 'featureFlags.emptyFlags.description': + "Les indicateurs de fonctionnalités permettent aux plugins d'enregistrer des fonctionnalités dans Backstage afin que les utilisateurs puissent les activer. Vous pouvez utiliser cela pour séparer la logique de votre code pour les tests A/B manuels, etc.", + 'featureFlags.emptyFlags.action.title': + "Un exemple de la manière d'ajouter un indicateur de fonctionnalité est présenté ci-dessous :", + 'featureFlags.emptyFlags.action.readMoreButtonTitle': 'En savoir plus', + 'featureFlags.filterTitle': 'Filtre', + 'featureFlags.clearFilter': 'Effacer le filtre', + 'featureFlags.flagItem.title.disable': 'Désactiver', + 'featureFlags.flagItem.title.enable': 'Activer', + 'featureFlags.flagItem.subtitle.registeredInApplication': + "Enregistré dans l'application", + 'featureFlags.flagItem.subtitle.registeredInPlugin': + 'Enregistré dans le plugin {{pluginId}}', + 'settingsLayout.title': 'Paramètres', + 'profileCard.title': 'Profil', + 'appearanceCard.title': 'Apparence', + }, +}); diff --git a/packages/app-next/src/translations/user-settings/user-settings-it.ts b/packages/app-next/src/translations/user-settings/user-settings-it.ts new file mode 100644 index 0000000000..590c306a76 --- /dev/null +++ b/packages/app-next/src/translations/user-settings/user-settings-it.ts @@ -0,0 +1,26 @@ +/* + * 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 { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { userSettingsTranslationRef } from '@backstage/plugin-user-settings'; + +export default createTranslationMessages({ + ref: userSettingsTranslationRef, + full: false, + messages: { + sidebarTitle: 'Impostazioni', + }, +}); diff --git a/packages/app-next/src/translations/user-settings/user-settings.ts b/packages/app-next/src/translations/user-settings/user-settings.ts new file mode 100644 index 0000000000..918a5d3bba --- /dev/null +++ b/packages/app-next/src/translations/user-settings/user-settings.ts @@ -0,0 +1,14 @@ +import { createTranslationResource } from '@backstage/core-plugin-api/alpha'; +import { userSettingsTranslationRef } from '@backstage/plugin-user-settings'; + +export const userSettingsTranslations = createTranslationResource({ + ref: userSettingsTranslationRef, + translations: { + de: () => import('./user-settings-de'), + en: () => import('./user-settings-en'), + es: () => import('./user-settings-es'), + fr: () => import('./user-settings-fr'), + it: () => import('./user-settings-it'), + ja: () => import('./ja'), + }, +}); diff --git a/packages/app-next/tsconfig.json b/packages/app-next/tsconfig.json new file mode 100644 index 0000000000..e9276c0509 --- /dev/null +++ b/packages/app-next/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@backstage/cli/config/tsconfig.json", + "include": ["src"], + "exclude": ["node_modules"], + "compilerOptions": { + "jsx": "preserve", + "outDir": "../../dist-types/packages/app-next", + "rootDir": "." + } +} diff --git a/yarn.lock b/yarn.lock index 9628775c15..33163d32cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3807,7 +3807,7 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-app-react@npm:^0.2.1, @backstage/plugin-app-react@npm:^0.2.4": +"@backstage/plugin-app-react@npm:0.2.4, @backstage/plugin-app-react@npm:^0.2.1, @backstage/plugin-app-react@npm:^0.2.4": version: 0.2.4 resolution: "@backstage/plugin-app-react@npm:0.2.4" dependencies: @@ -11309,6 +11309,50 @@ __metadata: languageName: node linkType: hard +"@red-hat-developer-hub/backstage-plugin-app-auth@npm:0.1.0": + version: 0.1.0 + resolution: "@red-hat-developer-hub/backstage-plugin-app-auth@npm:0.1.0" + dependencies: + "@backstage/core-app-api": "npm:^1.20.2" + "@backstage/core-components": "npm:^0.18.11" + "@backstage/core-plugin-api": "npm:^1.12.7" + "@backstage/frontend-plugin-api": "npm:^0.17.2" + "@backstage/plugin-app": "npm:^0.5.0" + "@mui/material": "npm:5.18.0" + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + checksum: 10c0/966641ff36d3c0ba5ecb885dde7933a7fbabaadf61c0b66360560013d12f993fd45622bc58ef72388615f7096bfba6974d4d0a41d5177255af3737ec817d8e68 + languageName: node + linkType: hard + +"@red-hat-developer-hub/backstage-plugin-app-integrations@npm:0.1.0": + version: 0.1.0 + resolution: "@red-hat-developer-hub/backstage-plugin-app-integrations@npm:0.1.0" + dependencies: + "@backstage/core-plugin-api": "npm:^1.12.7" + "@backstage/frontend-plugin-api": "npm:^0.17.2" + "@backstage/integration-react": "npm:^1.2.19" + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + checksum: 10c0/8853a2abe4bec66a5fe356917215863840bccc8241ab2148afd62f49a102940d8846a9db6ee9295b46d72b474b46051c3203c9e38fbdf1c84163e056c5b9d53e + languageName: node + linkType: hard + +"@red-hat-developer-hub/backstage-plugin-app-react@npm:0.1.0": + version: 0.1.0 + resolution: "@red-hat-developer-hub/backstage-plugin-app-react@npm:0.1.0" + dependencies: + "@backstage/core-plugin-api": "npm:^1.12.7" + "@backstage/frontend-plugin-api": "npm:^0.17.2" + "@backstage/plugin-app-react": "npm:^0.2.4" + "@backstage/version-bridge": "npm:^1.0.12" + "@mui/material": "npm:5.18.0" + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + checksum: 10c0/5f1448076a831ed77c6c2dc7b3bf600afe87a46f7b6b90a2411238ddd5949687d2a5e19bab4718aedcd80587d7478621f03a460514f15d1f4d37395ee8d797ae + languageName: node + linkType: hard + "@red-hat-developer-hub/backstage-plugin-theme@npm:0.14.7": version: 0.14.7 resolution: "@red-hat-developer-hub/backstage-plugin-theme@npm:0.14.7" @@ -15843,8 +15887,10 @@ __metadata: "@backstage/frontend-defaults": "npm:0.5.3" "@backstage/frontend-dynamic-feature-loader": "npm:0.1.13" "@backstage/frontend-plugin-api": "npm:0.17.2" + "@backstage/frontend-test-utils": "npm:0.6.1" "@backstage/integration-react": "npm:1.2.19" "@backstage/plugin-app": "npm:0.5.0" + "@backstage/plugin-app-react": "npm:0.2.4" "@backstage/plugin-app-visualizer": "npm:0.2.5" "@backstage/plugin-auth-react": "npm:0.1.28" "@backstage/plugin-catalog": "npm:2.0.6" @@ -15866,7 +15912,13 @@ __metadata: "@material-ui/core": "npm:4.12.4" "@material-ui/icons": "npm:4.11.3" "@material-ui/lab": "npm:4.0.0-alpha.61" + "@mui/icons-material": "npm:5.18.0" + "@mui/material": "npm:5.18.0" "@octokit/rest": "npm:19.0.13" + "@red-hat-developer-hub/backstage-plugin-app-auth": "npm:0.1.0" + "@red-hat-developer-hub/backstage-plugin-app-integrations": "npm:0.1.0" + "@red-hat-developer-hub/backstage-plugin-app-react": "npm:0.1.0" + "@red-hat-developer-hub/backstage-plugin-theme": "npm:0.14.7" "@testing-library/dom": "npm:9.3.4" "@testing-library/jest-dom": "npm:6.9.1" "@testing-library/react": "npm:14.3.1" @@ -15882,6 +15934,8 @@ __metadata: react-router: "npm:6.30.4" react-router-dom: "npm:6.30.4" react-use: "npm:17.6.1" + tss-react: "npm:4.9.21" + typescript: "npm:5.9.3" zen-observable: "npm:0.10.0" languageName: unknown linkType: soft From c6fdccbcd5b5761309b525efebf191f3d7a75068 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 6 Aug 2026 10:09:28 -0400 Subject: [PATCH 02/48] chore: modify schemaLocator for initial NFS migration Assisted-By: Cursor Signed-off-by: Frank Kong rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- packages/backend/src/index.ts | 12 +---- packages/backend/src/schemaLocator.test.ts | 52 ++++++++++++++++++++++ packages/backend/src/schemaLocator.ts | 34 ++++++++++++++ 3 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 packages/backend/src/schemaLocator.test.ts create mode 100644 packages/backend/src/schemaLocator.ts diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 7f0acf4ca6..b668818b4b 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -6,7 +6,6 @@ import { dynamicPluginsFrontendServiceRef, } from '@backstage/backend-dynamic-feature-service'; import { createServiceFactory } from '@backstage/backend-plugin-api'; -import { PackageRoles } from '@backstage/cli-node'; import * as path from 'path'; @@ -18,6 +17,7 @@ import { rbacDynamicPluginsProvider, } from './modules'; import { userSettingsBackend } from './modules/userSettings'; +import { schemaLocator } from './schemaLocator'; // Create a logger to cover logging static initialization tasks const staticLogger = WinstonLogger.create({ @@ -39,15 +39,7 @@ defaultServiceFactories.forEach(serviceFactory => { backend.add( dynamicPluginsFeatureLoader({ - schemaLocator(pluginPackage) { - const platform = PackageRoles.getRoleInfo( - pluginPackage.manifest.backstage.role, - ).platform; - return path.join( - platform === 'node' ? 'dist' : 'dist-scalprum', - 'configSchema.json', - ); - }, + schemaLocator, moduleLoader: logger => new CommonJSModuleLoader({ diff --git a/packages/backend/src/schemaLocator.test.ts b/packages/backend/src/schemaLocator.test.ts new file mode 100644 index 0000000000..895a7b71db --- /dev/null +++ b/packages/backend/src/schemaLocator.test.ts @@ -0,0 +1,52 @@ +import type { ScannedPluginPackage } from '@backstage/backend-dynamic-feature-service'; + +import * as fs from 'fs'; +import * as path from 'path'; +import * as url from 'url'; + +import { schemaLocator } from './schemaLocator'; + +jest.mock('fs'); + +function createPluginPackage(role: string): ScannedPluginPackage { + return { + location: url.pathToFileURL('/plugins/example-plugin'), + manifest: { + name: 'example-plugin', + backstage: { role }, + }, + } as unknown as ScannedPluginPackage; +} + +describe('schemaLocator', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('prefers the modern dist/.config-schema.json path when it exists', () => { + const existsSync = jest.spyOn(fs, 'existsSync').mockReturnValue(true); + + expect(schemaLocator(createPluginPackage('backend-plugin'))).toBe( + path.join('dist', '.config-schema.json'), + ); + expect(existsSync).toHaveBeenCalledWith( + path.resolve('/plugins/example-plugin', 'dist', '.config-schema.json'), + ); + }); + + it('falls back to the legacy dist/configSchema.json path for backend (node) plugins', () => { + jest.spyOn(fs, 'existsSync').mockReturnValue(false); + + expect(schemaLocator(createPluginPackage('backend-plugin'))).toBe( + path.join('dist', 'configSchema.json'), + ); + }); + + it('falls back to the legacy dist-scalprum/configSchema.json path for frontend (web) plugins', () => { + jest.spyOn(fs, 'existsSync').mockReturnValue(false); + + expect(schemaLocator(createPluginPackage('frontend-plugin'))).toBe( + path.join('dist-scalprum', 'configSchema.json'), + ); + }); +}); diff --git a/packages/backend/src/schemaLocator.ts b/packages/backend/src/schemaLocator.ts new file mode 100644 index 0000000000..8829d16a78 --- /dev/null +++ b/packages/backend/src/schemaLocator.ts @@ -0,0 +1,34 @@ +import { PackageRoles } from '@backstage/cli-node'; + +import * as fs from 'fs'; +import * as path from 'path'; +import * as url from 'url'; + +import type { ScannedPluginPackage } from '@backstage/backend-dynamic-feature-service'; + +/** + * Locates the config schema file for a scanned dynamic plugin package. + * + * RHIDP-15079 (partial): prefers the modern `dist/.config-schema.json` path + * written by `@backstage/cli`'s `cli-module-build` bundler for both frontend + * and backend packages, falling back to the legacy per-platform path written + * by the old janus-idp/cli (Scalprum) toolchain. + * + * The legacy fallback should be removed entirely once OFS (`packages/app`) + * is deleted in RHIDP-15078/RHIDP-15079. + */ +export function schemaLocator(pluginPackage: ScannedPluginPackage): string { + const pluginLocation = url.fileURLToPath(pluginPackage.location); + const modernSchemaPath = path.join('dist', '.config-schema.json'); + if (fs.existsSync(path.resolve(pluginLocation, modernSchemaPath))) { + return modernSchemaPath; + } + + const platform = PackageRoles.getRoleInfo( + pluginPackage.manifest.backstage.role, + ).platform; + return path.join( + platform === 'node' ? 'dist' : 'dist-scalprum', + 'configSchema.json', + ); +} From 6b123757b70aa6dd3a3944149e8d2a9fa473a642 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Fri, 7 Aug 2026 10:50:32 -0400 Subject: [PATCH 03/48] chore: migrate `app-next` to `app` and `app` to `app-legacy` Assisted-By: Cursor Signed-off-by: Frank Kong rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- package.json | 4 +- packages/{app => app-legacy}/.eslintignore | 0 packages/app-legacy/.eslintrc.js | 12 + .../{app => app-legacy}/.lintstagedrc.json | 0 packages/app-legacy/.prettierignore | 3 + packages/{app => app-legacy}/.prettierrc.js | 0 packages/{app => app-legacy}/CHANGELOG.md | 0 packages/app-legacy/config.d.ts | 346 ++++++++++++++++++ .../{app-next => app-legacy}/package.json | 103 +++--- .../public/android-chrome-192x192.png | Bin .../app-legacy/public/apple-touch-icon.png | Bin 0 -> 9845 bytes .../public/favicon-16x16.png | Bin .../public/favicon-192x192.png | Bin .../public/favicon-32x32.png | Bin packages/app-legacy/public/favicon.ico | Bin 0 -> 9662 bytes .../public/fonts/RedHatText-Regular.otf | Bin .../public/fonts/RedHatText-Regular.ttf | Bin .../public/fonts/RedHatText-Regular.woff2 | Bin .../public/homepage/README.md | 0 .../public/homepage/data.json | 0 .../public/homepage/icons/argo-icon-color.svg | 0 .../homepage/icons/icons8/developerhub.png | Bin .../homepage/icons/icons8/devspaces.png | Bin .../homepage/icons/icons8/openshift.png | Bin .../homepage/icons/icons8/registry-editor.png | Bin .../public/homepage/icons/icons8/safe-out.png | Bin .../icons/icons8/security-checked.png | Bin .../homepage/icons/icons8/system-task.png | Bin .../public/homepage/icons/keycloak.svg | 0 .../2020_RHD_Coding_imageA@2x.png | Bin .../openshift-learning/ST-spring-boot_2x.png | Bin .../icons/openshift-learning/hello-kafka.png | Bin .../public/homepage/icons/podman_logo.svg | 0 .../public/homepage/icons/quay.svg | 0 .../public/homepage/icons/sonarqube.svg | 0 packages/app-legacy/public/index.html | 60 +++ .../public/learning-paths/data.json | 0 .../{app => app-legacy}/public/manifest.json | 0 packages/app-legacy/public/robots.txt | 2 + .../public/safari-pinned-tab.svg | 0 packages/app-legacy/src/App.test.tsx | 43 +++ packages/app-legacy/src/App.tsx | 31 ++ .../src/api/AuthApiRefs.ts | 0 .../src/api/LearningPathApiClient.ts | 0 packages/{app => app-legacy}/src/api/index.ts | 0 packages/{app => app-legacy}/src/apis.ts | 0 .../{app => app-legacy}/src/bootstrap.tsx | 0 packages/app-legacy/src/build-metadata.json | 9 + packages/{app => app-legacy}/src/common.tsx | 0 .../src/components/AppBase/AppBase.tsx | 0 .../src/components/AppBase/index.ts | 0 .../components/CustomIcons/DeveloperHub.tsx | 0 .../components/DynamicRoot/CommonIcons.tsx | 0 .../DynamicRoot/DynamicRoot.test.tsx | 0 .../components/DynamicRoot/DynamicRoot.tsx | 0 .../src/components/DynamicRoot/Loader.tsx | 0 .../components/DynamicRoot/ScalprumRoot.tsx | 0 .../DynamicRoot/defaultAppComponents.tsx | 0 .../src/components/DynamicRoot/index.ts | 0 .../src/components/ErrorPages/ErrorPage.tsx | 0 .../ErrorPages/NotFoundErrorPage.tsx | 0 .../errorButtons/ContactSupportButton.tsx | 0 .../ErrorPages/errorButtons/GoBackButton.tsx | 0 .../collaboration/collaboration-dark.svg | 0 .../collaboration/collaboration-light.svg | 0 .../collaboration/collaboration.tsx | 0 .../ErrorPages/illustrations/illustration.tsx | 0 .../src/components/Root/ApplicationDrawer.tsx | 0 .../components/Root/ApplicationHeaders.tsx | 0 .../Root/ApplicationListener.test.tsx | 0 .../components/Root/ApplicationListener.tsx | 0 .../Root/ApplicationProvider.test.tsx | 0 .../components/Root/ApplicationProvider.tsx | 0 .../src/components/Root/ConfigUpdater.tsx | 0 .../Root/CustomSidebarItem.test.tsx | 0 .../src/components/Root/CustomSidebarItem.tsx | 0 .../src/components/Root/LogoFull.tsx | 0 .../src/components/Root/LogoIcon.tsx | 0 .../src/components/Root/MenuIcon.tsx | 0 .../src/components/Root/ResizableDrawer.tsx | 0 .../src/components/Root/Root.tsx | 0 .../src/components/Root/SidebarLogo.test.tsx | 0 .../src/components/Root}/SidebarLogo.tsx | 0 .../src/components/Root/index.ts | 0 .../src/components/SignInPage/SignInPage.tsx | 0 .../UserSettings/GeneralPage.test.tsx | 0 .../components/UserSettings/GeneralPage.tsx | 0 .../components/UserSettings/InfoCard.test.tsx | 0 .../src/components/UserSettings/InfoCard.tsx | 0 .../components/UserSettings/SettingsPages.tsx | 0 .../catalog/EntityPage/ApiTabContent.tsx | 0 .../ContextMenuAwareEntityLayout.tsx | 0 .../EntityPage/DefinitionTabContent.tsx | 0 .../EntityPage/DependenciesTabContent.tsx | 0 .../catalog/EntityPage/DiagramTabContent.tsx | 0 .../catalog/EntityPage/DynamicEntityTab.tsx | 0 .../catalog/EntityPage/EntityPage.tsx | 0 .../catalog/EntityPage/OverviewTabContent.tsx | 0 .../catalog/EntityPage/defaultTabs.tsx | 0 .../components/catalog/EntityPage/index.tsx | 0 .../catalog/EntityPage/utils.test.tsx | 0 .../components/catalog/EntityPage/utils.tsx | 0 .../src/components/catalog/Grid/Grid.tsx | 0 .../src/components/catalog/Grid/index.ts | 0 .../catalog/filters/CustomCatalogFilters.tsx | 0 .../filters/CustomEntityTagPicker.test.ts | 0 .../catalog/filters/CustomEntityTagPicker.tsx | 0 .../src/components/catalog/utils.tsx | 0 .../learningPaths/LearningPathsPage.test.tsx | 0 .../learningPaths/LearningPathsPage.tsx | 0 .../src/components/search/SearchPage.tsx | 0 packages/{app => app-legacy}/src/consts.ts | 0 .../src/hooks/useLanguagePreference.test.ts | 0 .../src/hooks/useLanguagePreference.ts | 0 .../src/hooks/useLearningPathData.test.ts | 0 .../src/hooks/useLearningPathData.ts | 0 .../src/hooks/useThemedConfig.test.tsx | 89 +++++ .../app-legacy/src/hooks/useThemedConfig.ts | 84 +++++ .../app-legacy/src/hooks/useTranslation.ts | 26 ++ packages/app-legacy/src/index.tsx | 1 + packages/app-legacy/src/setupTests.ts | 1 + .../catalog-import/catalog-import-en.ts | 0 .../catalog-import/catalog-import.ts | 0 .../src/translations/catalog-import/de.ts | 0 .../src/translations/catalog-import/es.ts | 0 .../src/translations/catalog-import/fr.ts | 0 .../src/translations/catalog-import/it.ts | 0 .../src/translations/catalog-import/ja.ts | 0 .../src/translations/catalog/catalog-en.ts | 0 .../src/translations/catalog/catalog.ts | 0 .../src/translations/catalog/de.ts | 0 .../src/translations/catalog/es.ts | 0 .../src/translations/catalog/fr.ts | 0 .../src/translations/catalog/it.ts | 0 .../src/translations/catalog/ja.ts | 0 .../core-components/core-components-en.ts | 0 .../core-components/core-components.ts | 0 .../src/translations/core-components/de.ts | 0 .../src/translations/core-components/es.ts | 0 .../src/translations/core-components/fr.ts | 0 .../src/translations/core-components/it.ts | 0 .../src/translations/core-components/ja.ts | 0 .../src/translations/rhdh/de.ts | 0 .../src/translations/rhdh/es.ts | 0 .../src/translations/rhdh/fr.ts | 0 .../src/translations/rhdh/index.ts | 0 .../src/translations/rhdh/it.ts | 0 .../src/translations/rhdh/ja.ts | 0 .../src/translations/rhdh/ref.ts | 0 .../src/translations/scaffolder/de.ts | 0 .../src/translations/scaffolder/es.ts | 0 .../src/translations/scaffolder/fr.ts | 0 .../src/translations/scaffolder/it.ts | 0 .../src/translations/scaffolder/ja.ts | 0 .../translations/scaffolder/scaffolder-en.ts | 0 .../src/translations/scaffolder/scaffolder.ts | 0 .../src/translations/search/ja.ts | 0 .../src/translations/search/search-de.ts | 0 .../src/translations/search/search-en.ts | 0 .../src/translations/search/search-es.ts | 0 .../src/translations/search/search-fr.ts | 0 .../src/translations/search/search-it.ts | 0 .../src/translations/search/search.ts | 0 .../src/translations/user-settings/ja.ts | 0 .../user-settings/user-settings-de.ts | 0 .../user-settings/user-settings-en.ts | 0 .../user-settings/user-settings-es.ts | 0 .../user-settings/user-settings-fr.ts | 0 .../user-settings/user-settings-it.ts | 0 .../user-settings/user-settings.ts | 0 packages/{app => app-legacy}/src/types.d.ts | 0 .../{app => app-legacy}/src/types/types.ts | 0 .../src/utils/dynamicUI/bindAppRoutes.ts | 0 .../dynamicUI/extractDynamicConfig.test.ts | 0 .../utils/dynamicUI/extractDynamicConfig.ts | 0 .../extractDynamicConfigFrontend.test.ts | 0 .../dynamicUI/extractDynamicConfigFrontend.ts | 0 .../utils/dynamicUI/getDynamicRootConfig.ts | 0 .../utils/dynamicUI/getMountPointData.test.ts | 0 .../src/utils/dynamicUI/getMountPointData.ts | 0 .../dynamicUI/initializeRemotePlugins.ts | 0 .../utils/dynamicUI/overrideBaseUrlConfigs.ts | 0 .../src/utils/language/language.test.ts | 0 .../src/utils/language/language.ts | 0 .../src/utils/test/TestRoot.tsx | 0 .../translations/fetchOverrideTranslations.ts | 0 .../src/utils/translations/index.ts | 0 .../translations/staticTranslationConfigs.ts | 0 .../translationResourceGenerator.test.ts | 0 .../translationResourceGenerator.ts | 0 .../translationResourceProcessor.ts | 0 .../utils/translations/translationUtils.ts | 0 .../{app-next => app-legacy}/tsconfig.json | 2 +- packages/{app => app-legacy}/turbo.json | 2 +- packages/app-next/.eslintrc.js | 5 - packages/app-next/config.d.ts | 49 --- packages/app-next/public/apple-touch-icon.png | Bin 13575 -> 0 bytes packages/app-next/public/favicon.ico | Bin 15086 -> 0 bytes packages/app-next/public/index.html | 24 -- packages/app-next/public/robots.txt | 1 - packages/app-next/src/App.test.tsx | 33 -- packages/app-next/src/App.tsx | 39 -- .../app-next/src/hooks/useThemedConfig.ts | 39 -- packages/app-next/src/hooks/useTranslation.ts | 10 - packages/app-next/src/index.tsx | 6 - packages/app-next/src/setupTests.ts | 5 - packages/app/.eslintrc.js | 17 +- packages/{app-next => app}/README.md | 13 +- packages/app/config.d.ts | 268 -------------- packages/app/package.json | 109 +++--- packages/app/public/apple-touch-icon.png | Bin 9845 -> 13575 bytes .../public/favicon-96x96.png | Bin packages/app/public/favicon.ico | Bin 9662 -> 15086 bytes packages/{app-next => app}/public/favicon.svg | 0 packages/app/public/index.html | 54 +-- packages/app/public/robots.txt | 1 - .../{app-next => app}/public/site.webmanifest | 0 .../public/web-app-manifest-192x192.png | Bin .../public/web-app-manifest-512x512.png | Bin packages/app/src/App.test.tsx | 42 +-- packages/app/src/App.tsx | 64 ++-- .../src/apis/LearningPathApiClient.ts | 0 .../{app-next => app}/src/apis/apisModule.ts | 0 .../app/src/hooks/useThemedConfig.test.tsx | 89 ----- packages/app/src/hooks/useThemedConfig.ts | 45 --- packages/app/src/index.tsx | 7 +- .../src/modules/nav/LogoFull.tsx | 2 +- .../src/modules/nav/LogoIcon.tsx | 2 +- .../src/modules/nav/Sidebar.tsx | 0 .../src/modules/nav/SidebarLogo.test.tsx | 4 +- .../Root => modules/nav}/SidebarLogo.tsx | 0 .../src/modules/nav/index.ts | 2 +- .../src/modules/user-settings/GeneralPage.tsx | 28 ++ .../modules/user-settings/InfoCard.test.tsx | 185 ++++++++++ .../src/modules/user-settings/InfoCard.tsx | 164 +++++++++ .../app/src/modules/user-settings/index.tsx | 28 ++ .../app/src/modules/user-settings/types.ts | 7 + packages/app/src/setupTests.ts | 4 + .../src/translations/translationsModule.ts | 2 +- packages/backend/package.json | 5 +- packages/backend/src/schemaLocator.ts | 2 +- yarn.lock | 87 +++-- 242 files changed, 1370 insertions(+), 890 deletions(-) rename packages/{app => app-legacy}/.eslintignore (100%) create mode 100644 packages/app-legacy/.eslintrc.js rename packages/{app => app-legacy}/.lintstagedrc.json (100%) create mode 100644 packages/app-legacy/.prettierignore rename packages/{app => app-legacy}/.prettierrc.js (100%) rename packages/{app => app-legacy}/CHANGELOG.md (100%) create mode 100755 packages/app-legacy/config.d.ts rename packages/{app-next => app-legacy}/package.json (59%) rename packages/{app => app-legacy}/public/android-chrome-192x192.png (100%) create mode 100644 packages/app-legacy/public/apple-touch-icon.png rename packages/{app => app-legacy}/public/favicon-16x16.png (100%) rename packages/{app => app-legacy}/public/favicon-192x192.png (100%) rename packages/{app => app-legacy}/public/favicon-32x32.png (100%) create mode 100644 packages/app-legacy/public/favicon.ico rename packages/{app => app-legacy}/public/fonts/RedHatText-Regular.otf (100%) rename packages/{app => app-legacy}/public/fonts/RedHatText-Regular.ttf (100%) rename packages/{app => app-legacy}/public/fonts/RedHatText-Regular.woff2 (100%) rename packages/{app => app-legacy}/public/homepage/README.md (100%) rename packages/{app => app-legacy}/public/homepage/data.json (100%) rename packages/{app => app-legacy}/public/homepage/icons/argo-icon-color.svg (100%) rename packages/{app => app-legacy}/public/homepage/icons/icons8/developerhub.png (100%) rename packages/{app => app-legacy}/public/homepage/icons/icons8/devspaces.png (100%) rename packages/{app => app-legacy}/public/homepage/icons/icons8/openshift.png (100%) rename packages/{app => app-legacy}/public/homepage/icons/icons8/registry-editor.png (100%) rename packages/{app => app-legacy}/public/homepage/icons/icons8/safe-out.png (100%) rename packages/{app => app-legacy}/public/homepage/icons/icons8/security-checked.png (100%) rename packages/{app => app-legacy}/public/homepage/icons/icons8/system-task.png (100%) rename packages/{app => app-legacy}/public/homepage/icons/keycloak.svg (100%) rename packages/{app => app-legacy}/public/homepage/icons/openshift-learning/2020_RHD_Coding_imageA@2x.png (100%) rename packages/{app => app-legacy}/public/homepage/icons/openshift-learning/ST-spring-boot_2x.png (100%) rename packages/{app => app-legacy}/public/homepage/icons/openshift-learning/hello-kafka.png (100%) rename packages/{app => app-legacy}/public/homepage/icons/podman_logo.svg (100%) rename packages/{app => app-legacy}/public/homepage/icons/quay.svg (100%) rename packages/{app => app-legacy}/public/homepage/icons/sonarqube.svg (100%) create mode 100644 packages/app-legacy/public/index.html rename packages/{app => app-legacy}/public/learning-paths/data.json (100%) rename packages/{app => app-legacy}/public/manifest.json (100%) create mode 100644 packages/app-legacy/public/robots.txt rename packages/{app => app-legacy}/public/safari-pinned-tab.svg (100%) create mode 100644 packages/app-legacy/src/App.test.tsx create mode 100644 packages/app-legacy/src/App.tsx rename packages/{app => app-legacy}/src/api/AuthApiRefs.ts (100%) rename packages/{app => app-legacy}/src/api/LearningPathApiClient.ts (100%) rename packages/{app => app-legacy}/src/api/index.ts (100%) rename packages/{app => app-legacy}/src/apis.ts (100%) rename packages/{app => app-legacy}/src/bootstrap.tsx (100%) create mode 100644 packages/app-legacy/src/build-metadata.json rename packages/{app => app-legacy}/src/common.tsx (100%) rename packages/{app => app-legacy}/src/components/AppBase/AppBase.tsx (100%) rename packages/{app => app-legacy}/src/components/AppBase/index.ts (100%) rename packages/{app => app-legacy}/src/components/CustomIcons/DeveloperHub.tsx (100%) rename packages/{app => app-legacy}/src/components/DynamicRoot/CommonIcons.tsx (100%) rename packages/{app => app-legacy}/src/components/DynamicRoot/DynamicRoot.test.tsx (100%) rename packages/{app => app-legacy}/src/components/DynamicRoot/DynamicRoot.tsx (100%) rename packages/{app => app-legacy}/src/components/DynamicRoot/Loader.tsx (100%) rename packages/{app => app-legacy}/src/components/DynamicRoot/ScalprumRoot.tsx (100%) rename packages/{app => app-legacy}/src/components/DynamicRoot/defaultAppComponents.tsx (100%) rename packages/{app => app-legacy}/src/components/DynamicRoot/index.ts (100%) rename packages/{app => app-legacy}/src/components/ErrorPages/ErrorPage.tsx (100%) rename packages/{app => app-legacy}/src/components/ErrorPages/NotFoundErrorPage.tsx (100%) rename packages/{app => app-legacy}/src/components/ErrorPages/errorButtons/ContactSupportButton.tsx (100%) rename packages/{app => app-legacy}/src/components/ErrorPages/errorButtons/GoBackButton.tsx (100%) rename packages/{app => app-legacy}/src/components/ErrorPages/illustrations/collaboration/collaboration-dark.svg (100%) rename packages/{app => app-legacy}/src/components/ErrorPages/illustrations/collaboration/collaboration-light.svg (100%) rename packages/{app => app-legacy}/src/components/ErrorPages/illustrations/collaboration/collaboration.tsx (100%) rename packages/{app => app-legacy}/src/components/ErrorPages/illustrations/illustration.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/ApplicationDrawer.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/ApplicationHeaders.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/ApplicationListener.test.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/ApplicationListener.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/ApplicationProvider.test.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/ApplicationProvider.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/ConfigUpdater.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/CustomSidebarItem.test.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/CustomSidebarItem.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/LogoFull.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/LogoIcon.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/MenuIcon.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/ResizableDrawer.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/Root.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/SidebarLogo.test.tsx (100%) rename packages/{app-next/src/modules/nav => app-legacy/src/components/Root}/SidebarLogo.tsx (100%) rename packages/{app => app-legacy}/src/components/Root/index.ts (100%) rename packages/{app => app-legacy}/src/components/SignInPage/SignInPage.tsx (100%) rename packages/{app => app-legacy}/src/components/UserSettings/GeneralPage.test.tsx (100%) rename packages/{app => app-legacy}/src/components/UserSettings/GeneralPage.tsx (100%) rename packages/{app => app-legacy}/src/components/UserSettings/InfoCard.test.tsx (100%) rename packages/{app => app-legacy}/src/components/UserSettings/InfoCard.tsx (100%) rename packages/{app => app-legacy}/src/components/UserSettings/SettingsPages.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/EntityPage/ApiTabContent.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/EntityPage/ContextMenuAwareEntityLayout.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/EntityPage/DefinitionTabContent.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/EntityPage/DependenciesTabContent.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/EntityPage/DiagramTabContent.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/EntityPage/DynamicEntityTab.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/EntityPage/EntityPage.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/EntityPage/OverviewTabContent.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/EntityPage/defaultTabs.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/EntityPage/index.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/EntityPage/utils.test.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/EntityPage/utils.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/Grid/Grid.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/Grid/index.ts (100%) rename packages/{app => app-legacy}/src/components/catalog/filters/CustomCatalogFilters.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/filters/CustomEntityTagPicker.test.ts (100%) rename packages/{app => app-legacy}/src/components/catalog/filters/CustomEntityTagPicker.tsx (100%) rename packages/{app => app-legacy}/src/components/catalog/utils.tsx (100%) rename packages/{app => app-legacy}/src/components/learningPaths/LearningPathsPage.test.tsx (100%) rename packages/{app => app-legacy}/src/components/learningPaths/LearningPathsPage.tsx (100%) rename packages/{app => app-legacy}/src/components/search/SearchPage.tsx (100%) rename packages/{app => app-legacy}/src/consts.ts (100%) rename packages/{app => app-legacy}/src/hooks/useLanguagePreference.test.ts (100%) rename packages/{app => app-legacy}/src/hooks/useLanguagePreference.ts (100%) rename packages/{app => app-legacy}/src/hooks/useLearningPathData.test.ts (100%) rename packages/{app => app-legacy}/src/hooks/useLearningPathData.ts (100%) rename packages/{app-next => app-legacy}/src/hooks/useThemedConfig.test.tsx (52%) create mode 100644 packages/app-legacy/src/hooks/useThemedConfig.ts create mode 100644 packages/app-legacy/src/hooks/useTranslation.ts create mode 100644 packages/app-legacy/src/index.tsx create mode 100644 packages/app-legacy/src/setupTests.ts rename packages/{app-next => app-legacy}/src/translations/catalog-import/catalog-import-en.ts (100%) rename packages/{app-next => app-legacy}/src/translations/catalog-import/catalog-import.ts (100%) rename packages/{app-next => app-legacy}/src/translations/catalog-import/de.ts (100%) rename packages/{app-next => app-legacy}/src/translations/catalog-import/es.ts (100%) rename packages/{app-next => app-legacy}/src/translations/catalog-import/fr.ts (100%) rename packages/{app-next => app-legacy}/src/translations/catalog-import/it.ts (100%) rename packages/{app-next => app-legacy}/src/translations/catalog-import/ja.ts (100%) rename packages/{app-next => app-legacy}/src/translations/catalog/catalog-en.ts (100%) rename packages/{app-next => app-legacy}/src/translations/catalog/catalog.ts (100%) rename packages/{app-next => app-legacy}/src/translations/catalog/de.ts (100%) rename packages/{app-next => app-legacy}/src/translations/catalog/es.ts (100%) rename packages/{app-next => app-legacy}/src/translations/catalog/fr.ts (100%) rename packages/{app-next => app-legacy}/src/translations/catalog/it.ts (100%) rename packages/{app-next => app-legacy}/src/translations/catalog/ja.ts (100%) rename packages/{app-next => app-legacy}/src/translations/core-components/core-components-en.ts (100%) rename packages/{app-next => app-legacy}/src/translations/core-components/core-components.ts (100%) rename packages/{app-next => app-legacy}/src/translations/core-components/de.ts (100%) rename packages/{app-next => app-legacy}/src/translations/core-components/es.ts (100%) rename packages/{app-next => app-legacy}/src/translations/core-components/fr.ts (100%) rename packages/{app-next => app-legacy}/src/translations/core-components/it.ts (100%) rename packages/{app-next => app-legacy}/src/translations/core-components/ja.ts (100%) rename packages/{app-next => app-legacy}/src/translations/rhdh/de.ts (100%) rename packages/{app-next => app-legacy}/src/translations/rhdh/es.ts (100%) rename packages/{app-next => app-legacy}/src/translations/rhdh/fr.ts (100%) rename packages/{app-next => app-legacy}/src/translations/rhdh/index.ts (100%) rename packages/{app-next => app-legacy}/src/translations/rhdh/it.ts (100%) rename packages/{app-next => app-legacy}/src/translations/rhdh/ja.ts (100%) rename packages/{app-next => app-legacy}/src/translations/rhdh/ref.ts (100%) rename packages/{app-next => app-legacy}/src/translations/scaffolder/de.ts (100%) rename packages/{app-next => app-legacy}/src/translations/scaffolder/es.ts (100%) rename packages/{app-next => app-legacy}/src/translations/scaffolder/fr.ts (100%) rename packages/{app-next => app-legacy}/src/translations/scaffolder/it.ts (100%) rename packages/{app-next => app-legacy}/src/translations/scaffolder/ja.ts (100%) rename packages/{app-next => app-legacy}/src/translations/scaffolder/scaffolder-en.ts (100%) rename packages/{app-next => app-legacy}/src/translations/scaffolder/scaffolder.ts (100%) rename packages/{app-next => app-legacy}/src/translations/search/ja.ts (100%) rename packages/{app-next => app-legacy}/src/translations/search/search-de.ts (100%) rename packages/{app-next => app-legacy}/src/translations/search/search-en.ts (100%) rename packages/{app-next => app-legacy}/src/translations/search/search-es.ts (100%) rename packages/{app-next => app-legacy}/src/translations/search/search-fr.ts (100%) rename packages/{app-next => app-legacy}/src/translations/search/search-it.ts (100%) rename packages/{app-next => app-legacy}/src/translations/search/search.ts (100%) rename packages/{app-next => app-legacy}/src/translations/user-settings/ja.ts (100%) rename packages/{app-next => app-legacy}/src/translations/user-settings/user-settings-de.ts (100%) rename packages/{app-next => app-legacy}/src/translations/user-settings/user-settings-en.ts (100%) rename packages/{app-next => app-legacy}/src/translations/user-settings/user-settings-es.ts (100%) rename packages/{app-next => app-legacy}/src/translations/user-settings/user-settings-fr.ts (100%) rename packages/{app-next => app-legacy}/src/translations/user-settings/user-settings-it.ts (100%) rename packages/{app-next => app-legacy}/src/translations/user-settings/user-settings.ts (100%) rename packages/{app => app-legacy}/src/types.d.ts (100%) rename packages/{app => app-legacy}/src/types/types.ts (100%) rename packages/{app => app-legacy}/src/utils/dynamicUI/bindAppRoutes.ts (100%) rename packages/{app => app-legacy}/src/utils/dynamicUI/extractDynamicConfig.test.ts (100%) rename packages/{app => app-legacy}/src/utils/dynamicUI/extractDynamicConfig.ts (100%) rename packages/{app => app-legacy}/src/utils/dynamicUI/extractDynamicConfigFrontend.test.ts (100%) rename packages/{app => app-legacy}/src/utils/dynamicUI/extractDynamicConfigFrontend.ts (100%) rename packages/{app => app-legacy}/src/utils/dynamicUI/getDynamicRootConfig.ts (100%) rename packages/{app => app-legacy}/src/utils/dynamicUI/getMountPointData.test.ts (100%) rename packages/{app => app-legacy}/src/utils/dynamicUI/getMountPointData.ts (100%) rename packages/{app => app-legacy}/src/utils/dynamicUI/initializeRemotePlugins.ts (100%) rename packages/{app => app-legacy}/src/utils/dynamicUI/overrideBaseUrlConfigs.ts (100%) rename packages/{app => app-legacy}/src/utils/language/language.test.ts (100%) rename packages/{app => app-legacy}/src/utils/language/language.ts (100%) rename packages/{app => app-legacy}/src/utils/test/TestRoot.tsx (100%) rename packages/{app => app-legacy}/src/utils/translations/fetchOverrideTranslations.ts (100%) rename packages/{app => app-legacy}/src/utils/translations/index.ts (100%) rename packages/{app => app-legacy}/src/utils/translations/staticTranslationConfigs.ts (100%) rename packages/{app => app-legacy}/src/utils/translations/translationResourceGenerator.test.ts (100%) rename packages/{app => app-legacy}/src/utils/translations/translationResourceGenerator.ts (100%) rename packages/{app => app-legacy}/src/utils/translations/translationResourceProcessor.ts (100%) rename packages/{app => app-legacy}/src/utils/translations/translationUtils.ts (100%) rename packages/{app-next => app-legacy}/tsconfig.json (76%) rename packages/{app => app-legacy}/turbo.json (69%) delete mode 100644 packages/app-next/.eslintrc.js delete mode 100644 packages/app-next/config.d.ts delete mode 100644 packages/app-next/public/apple-touch-icon.png delete mode 100644 packages/app-next/public/favicon.ico delete mode 100644 packages/app-next/public/index.html delete mode 100644 packages/app-next/public/robots.txt delete mode 100644 packages/app-next/src/App.test.tsx delete mode 100644 packages/app-next/src/App.tsx delete mode 100644 packages/app-next/src/hooks/useThemedConfig.ts delete mode 100644 packages/app-next/src/hooks/useTranslation.ts delete mode 100644 packages/app-next/src/index.tsx delete mode 100644 packages/app-next/src/setupTests.ts rename packages/{app-next => app}/README.md (71%) mode change 100755 => 100644 packages/app/config.d.ts rename packages/{app-next => app}/public/favicon-96x96.png (100%) rename packages/{app-next => app}/public/favicon.svg (100%) rename packages/{app-next => app}/public/site.webmanifest (100%) rename packages/{app-next => app}/public/web-app-manifest-192x192.png (100%) rename packages/{app-next => app}/public/web-app-manifest-512x512.png (100%) rename packages/{app-next => app}/src/apis/LearningPathApiClient.ts (100%) rename packages/{app-next => app}/src/apis/apisModule.ts (100%) rename packages/{app-next => app}/src/modules/nav/LogoFull.tsx (99%) rename packages/{app-next => app}/src/modules/nav/LogoIcon.tsx (96%) rename packages/{app-next => app}/src/modules/nav/Sidebar.tsx (100%) rename packages/{app-next => app}/src/modules/nav/SidebarLogo.test.tsx (96%) rename packages/app/src/{components/Root => modules/nav}/SidebarLogo.tsx (100%) rename packages/{app-next => app}/src/modules/nav/index.ts (88%) create mode 100644 packages/app/src/modules/user-settings/GeneralPage.tsx create mode 100644 packages/app/src/modules/user-settings/InfoCard.test.tsx create mode 100644 packages/app/src/modules/user-settings/InfoCard.tsx create mode 100644 packages/app/src/modules/user-settings/index.tsx create mode 100644 packages/app/src/modules/user-settings/types.ts rename packages/{app-next => app}/src/translations/translationsModule.ts (96%) diff --git a/package.json b/package.json index 163aac57c1..a82312b43b 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "scripts": { "prepare": "husky install", "start": "NODE_OPTIONS=--no-node-snapshot turbo run start --filter=backend", - "dev:legacy": "NODE_OPTIONS=--no-node-snapshot turbo run start --filter=backend --filter=app", - "dev": "NODE_OPTIONS=--no-node-snapshot turbo run start --filter=backend --filter=app-next", + "dev:legacy": "NODE_OPTIONS=--no-node-snapshot turbo run start --filter=backend --filter=app-legacy", + "dev": "NODE_OPTIONS=--no-node-snapshot turbo run start --filter=backend --filter=app", "build": "turbo run build", "build:dockerfile": "bash ./scripts/update-Dockerfile.sh", "tsc": "turbo run tsc", diff --git a/packages/app/.eslintignore b/packages/app-legacy/.eslintignore similarity index 100% rename from packages/app/.eslintignore rename to packages/app-legacy/.eslintignore diff --git a/packages/app-legacy/.eslintrc.js b/packages/app-legacy/.eslintrc.js new file mode 100644 index 0000000000..07630f21af --- /dev/null +++ b/packages/app-legacy/.eslintrc.js @@ -0,0 +1,12 @@ +const backstageConfig = require('@backstage/cli/config/eslint-factory')( + __dirname, +); + +module.exports = { + ...backstageConfig, + rules: { + ...backstageConfig.rules, + 'react/react-in-jsx-scope': 'off', + 'react/jsx-uses-react': 'off', + }, +}; diff --git a/packages/app/.lintstagedrc.json b/packages/app-legacy/.lintstagedrc.json similarity index 100% rename from packages/app/.lintstagedrc.json rename to packages/app-legacy/.lintstagedrc.json diff --git a/packages/app-legacy/.prettierignore b/packages/app-legacy/.prettierignore new file mode 100644 index 0000000000..9a88116918 --- /dev/null +++ b/packages/app-legacy/.prettierignore @@ -0,0 +1,3 @@ +dist +coverage +src/build-metadata.json diff --git a/packages/app/.prettierrc.js b/packages/app-legacy/.prettierrc.js similarity index 100% rename from packages/app/.prettierrc.js rename to packages/app-legacy/.prettierrc.js diff --git a/packages/app/CHANGELOG.md b/packages/app-legacy/CHANGELOG.md similarity index 100% rename from packages/app/CHANGELOG.md rename to packages/app-legacy/CHANGELOG.md diff --git a/packages/app-legacy/config.d.ts b/packages/app-legacy/config.d.ts new file mode 100755 index 0000000000..0ddc6b278a --- /dev/null +++ b/packages/app-legacy/config.d.ts @@ -0,0 +1,346 @@ +export interface Config { + /** Configurations for the backstage(janus) instance */ + developerHub?: { + /** + * The url of json data for customization. + * @visibility frontend + */ + proxyPath?: string; + /** + * Name of the Backstage flavor (e.g. backstage, rhdh, rhtap) + * @visibility frontend + */ + flavor?: string; + }; + app: { + branding?: { + /** + * Base64 URI for the full logo. If the value is a string, it is used as the logo for both themes. + * @visibility frontend + */ + // this config is copied to rhdh-plugins/global-header config.d.ts and should be kept in sync + fullLogo?: + | string + | { + /** + * Base64 URI for the logo in light theme + * @visibility frontend + */ + light: string; + /** + * Base64 URI for the logo in dark theme + * @visibility frontend + */ + dark: string; + }; + /** + * size Configuration for the full logo + * The following units are supported: , px, em, rem, + * @visibility frontend + */ + fullLogoWidth?: string | number; + /** + * Base64 URI for the icon logo. If the value is a string, it is used as the logo for both themes. + * @visibility frontend + */ + iconLogo?: + | string + | { + /** + * Base64 URI for the icon logo in light theme + * @visibility frontend + */ + light: string; + /** + * Base64 URI for the icon logo in dark theme + * @visibility frontend + */ + dark: string; + }; + /** + * @deepVisibility frontend + */ + theme?: { + [key: string]: unknown; + }; + }; + sidebar?: { + /** + * Show the logo in the sidebar + * @visibility frontend + */ + logo?: boolean; + /** + * Show the search in the sidebar + * @visibility frontend + */ + search?: boolean; + /** + * Show the settings in the sidebar + * @visibility frontend + */ + settings?: boolean; + /** + * Show the administration in the sidebar + * @visibility frontend + */ + administration?: boolean; + }; + quickstart?: Array; + /** + * Optional icon for quickstart. + * @visibility frontend + */ + icon?: string; + /** + * The description of quickstart. + * @visibility frontend + */ + description: string; + /** + * Optional translation key for the description. + * @visibility frontend + */ + descriptionKey?: string; + /** + * Optional action item for quickstart. + * @visibility frontend + */ + cta?: { + /** + * Action item text. + * @visibility frontend + */ + text: string; + /** + * Optional translation key for the action text. + * @visibility frontend + */ + textKey?: string; + /** + * Action item link. + * @visibility frontend + */ + link: string; + }; + }>; + }; + /** @deepVisibility frontend */ + dynamicPlugins: { + /** @deepVisibility frontend */ + frontend?: { + [key: string]: { + dynamicRoutes?: { + path: string; + module?: string; + importName?: string; + menuItem?: { + icon: string; + text: string; + enabled?: boolean; + }; + config: { + props?: { + [key: string]: string; + }; + }; + }[]; + routeBindings?: { + targets?: { + module?: string; + importName: string; + name?: string; + }[]; + bindings?: { + bindTarget: string; + bindMap: { + [key: string]: string; + }; + }[]; + }; + entityTabs?: { + path: string; + title: string; + mountPoint: string; + priority?: number; + }[]; + mountPoints?: { + mountPoint: string; + module?: string; + importName?: string; + config: { + layout?: { + [key: string]: + | string + | { + [key: string]: string; + }; + }; + props?: { + [key: string]: string; + }; + if?: { + allOf?: ( + | { + [key: string]: string | string[]; + } + | string + )[]; + anyOf?: ( + | { + [key: string]: string | string[]; + } + | string + )[]; + oneOf?: ( + | { + [key: string]: string | string[]; + } + | string + )[]; + }; + id?: string; + }; + }[]; + appIcons?: { + module?: string; + importName?: string; + name: string; + }[]; + apiFactories?: { + module?: string; + importName?: string; + }[]; + providerSettings?: { + title: string; + description: string; + provider: string; + }[]; + scaffolderFieldExtensions?: { + module?: string; + importName?: string; + }[]; + signInPage?: { + module?: string; + importName: string; + }; + techdocsAddons?: { + module?: string; + importName?: string; + config?: { + props?: { + [key: string]: string; + }; + }; + }[]; + themes?: { + module?: string; + id: string; + title: string; + variant: 'light' | 'dark'; + icon: string; + importName?: string; + }[]; + translationResources?: { + module?: string; + importName: string; + ref?: string; + }[]; + }; + }; + }; + /** + * The signInPage provider + * @visibility frontend + */ + signInPage?: string | string[]; + /** + * The option to includes transient parent groups when determining user group membership + * @visibility frontend + */ + includeTransitiveGroupOwnership?: boolean; + + /** + * Allows you to customize RHDH Metadata card information + * @deepVisibility frontend + */ + buildInfo?: { + /** + * Allows setting a title for the build information card + * @visibility frontend + */ + title: string; + /** + * Optional translation key for the title. + * @visibility frontend + */ + titleKey?: string; + /** + * Allows setting a content for the build information card + * @visibility frontend + */ + card: { [key: string]: string }; + /** + * Allows setting if the default build information (RHDH Version, Backstage Version, etc.) should be overridden + * Contents will be overridden if not set to false + * @default true + * @visibility frontend + */ + overrideBuildInfo?: boolean; + }; + + /** + * Internationalization (i18n) settings for the app + * Allows configuring supported languages + * @deepVisibility frontend + */ + i18n?: { + /** + * Allows listing the languages the app will support + * @visibility frontend + */ + locales: string[]; + /** + * Allows setting a default language for the app + * Will be set to `en` if not specified + * @default en + * @visibility frontend + */ + defaultLocale?: string; + /** + * Allows listing of paths to JSON files that contain translation overrides. + * These overrides let you replace or extend the default translations provided by plugins. + * @visibility frontend + */ + overrides?: string[]; + }; + /** + * Configuration options for your user settings. + * @deepVisibility frontend + */ + userSettings?: { + /** + * The persistence mode for user settings. + * @visibility frontend + */ + persistence: 'browser' | 'database'; + }; +} diff --git a/packages/app-next/package.json b/packages/app-legacy/package.json similarity index 59% rename from packages/app-next/package.json rename to packages/app-legacy/package.json index 9679c621bf..840243fd76 100644 --- a/packages/app-next/package.json +++ b/packages/app-legacy/package.json @@ -1,107 +1,100 @@ { - "name": "app-next", - "version": "0.0.23", + "name": "app-legacy", + "version": "1.0.1", "private": true, "bundled": true, "backstage": { "role": "frontend" }, - "repository": { - "type": "git", - "url": "https://github.com/redhat-developer/rhdh", - "directory": "packages/app-next" + "proxy": { + "/healthcheck": "http://localhost:7007" }, - "license": "Apache-2.0", - "files": [ - "dist" - ], "scripts": { - "build": "EXPERIMENTAL_MODULE_FEDERATION=true backstage-cli package build", + "start": "janus-cli package start", + "build": "janus-cli package build && node ../../scripts/generate-index-template.mjs", "clean": "backstage-cli package clean", - "lint": "backstage-cli package lint", + "test": "backstage-cli package test --passWithNoTests --coverage", "lint:check": "backstage-cli package lint", "lint:fix": "backstage-cli package lint --fix", - "start": "backstage-cli package start", - "test": "backstage-cli package test --passWithNoTests --coverage", - "tsc": "tsc" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] + "tsc": "tsc", + "prettier:check": "prettier --ignore-unknown --check .", + "prettier:fix": "prettier --ignore-unknown --write ." }, "dependencies": { + "@backstage-community/plugin-rbac-common": "1.26.1", "@backstage/app-defaults": "1.7.9", "@backstage/catalog-model": "1.9.0", - "@backstage/cli": "0.36.3", - "@backstage/cli-defaults": "0.1.3", "@backstage/config": "1.3.8", "@backstage/core-app-api": "1.20.2", - "@backstage/core-compat-api": "0.5.12", "@backstage/core-components": "0.18.11", "@backstage/core-plugin-api": "1.12.7", - "@backstage/frontend-app-api": "0.16.4", - "@backstage/frontend-defaults": "0.5.3", - "@backstage/frontend-dynamic-feature-loader": "0.1.13", - "@backstage/frontend-plugin-api": "0.17.2", "@backstage/integration-react": "1.2.19", - "@backstage/plugin-app": "0.5.0", - "@backstage/plugin-app-react": "0.2.4", - "@backstage/plugin-app-visualizer": "0.2.5", - "@backstage/plugin-auth-react": "0.1.28", + "@backstage/plugin-api-docs": "0.14.2", "@backstage/plugin-catalog": "2.0.6", "@backstage/plugin-catalog-common": "1.1.10", "@backstage/plugin-catalog-graph": "0.6.5", "@backstage/plugin-catalog-import": "0.13.14", "@backstage/plugin-catalog-react": "3.1.0", - "@backstage/plugin-catalog-unprocessed-entities": "0.2.32", + "@backstage/plugin-org": "0.7.5", "@backstage/plugin-permission-react": "0.5.2", "@backstage/plugin-scaffolder": "1.38.0", "@backstage/plugin-scaffolder-react": "2.0.1", "@backstage/plugin-search": "1.7.5", - "@backstage/plugin-search-common": "1.2.24", "@backstage/plugin-search-react": "1.11.5", "@backstage/plugin-user-settings": "0.9.4", "@backstage/theme": "0.7.3", "@backstage/ui": "0.16.0", - "@material-ui/core": "4.12.4", - "@material-ui/icons": "4.11.3", - "@material-ui/lab": "4.0.0-alpha.61", + "@emotion/react": "11.14.0", + "@emotion/styled": "11.14.1", "@mui/icons-material": "5.18.0", "@mui/material": "5.18.0", - "@octokit/rest": "19.0.13", - "@red-hat-developer-hub/backstage-plugin-app-auth": "0.1.0", - "@red-hat-developer-hub/backstage-plugin-app-integrations": "0.1.0", - "@red-hat-developer-hub/backstage-plugin-app-react": "0.1.0", + "@mui/styled-engine": "5.18.0", "@red-hat-developer-hub/backstage-plugin-theme": "0.14.7", - "history": "5.3.0", + "@red-hat-developer-hub/backstage-plugin-translations": "0.3.1", + "@red-hat-developer-hub/plugin-utils": "workspace:*", + "@scalprum/core": "0.10.0", + "@scalprum/react-core": "0.13.0", + "lodash": "4.18.1", "react": "18.3.1", "react-dom": "18.3.1", - "react-router": "6.30.4", "react-router-dom": "6.30.4", "react-use": "17.6.1", - "tss-react": "4.9.21", - "zen-observable": "0.10.0" + "tss-react": "4.9.21" }, "devDependencies": { + "@backstage/cli": "0.36.3", + "@backstage/cli-defaults": "0.1.3", "@backstage/frontend-test-utils": "0.6.1", "@backstage/test-utils": "1.7.19", + "@janus-idp/cli": "patch:@janus-idp/cli@npm%3A3.7.0#~/.yarn/patches/@janus-idp-cli-npm-3.7.0-f225cddfdb.patch", + "@scalprum/react-test-utils": "0.3.0", "@testing-library/dom": "9.3.4", "@testing-library/jest-dom": "6.9.1", "@testing-library/react": "14.3.1", + "@testing-library/react-hooks": "8.0.1", "@testing-library/user-event": "14.6.1", - "@types/jquery": "3.5.34", + "@types/node": "24.13.2", "@types/react": "18.3.31", "@types/react-dom": "18.3.7", - "@types/zen-observable": "0.8.7", - "cross-env": "7.0.3", + "eslint-plugin-unused-imports": "4.4.1", + "prettier": "3.8.4", "typescript": "5.9.3" - } + }, + "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", + "config.d.ts" + ], + "configSchema": "config.d.ts" } diff --git a/packages/app/public/android-chrome-192x192.png b/packages/app-legacy/public/android-chrome-192x192.png similarity index 100% rename from packages/app/public/android-chrome-192x192.png rename to packages/app-legacy/public/android-chrome-192x192.png diff --git a/packages/app-legacy/public/apple-touch-icon.png b/packages/app-legacy/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..719f50a963d4e4dbb79e1d5f20069646c005add6 GIT binary patch literal 9845 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 R0b9l(DvFv46>{dm{|f+>0=WPH literal 0 HcmV?d00001 diff --git a/packages/app/public/favicon-16x16.png b/packages/app-legacy/public/favicon-16x16.png similarity index 100% rename from packages/app/public/favicon-16x16.png rename to packages/app-legacy/public/favicon-16x16.png diff --git a/packages/app/public/favicon-192x192.png b/packages/app-legacy/public/favicon-192x192.png similarity index 100% rename from packages/app/public/favicon-192x192.png rename to packages/app-legacy/public/favicon-192x192.png diff --git a/packages/app/public/favicon-32x32.png b/packages/app-legacy/public/favicon-32x32.png similarity index 100% rename from packages/app/public/favicon-32x32.png rename to packages/app-legacy/public/favicon-32x32.png diff --git a/packages/app-legacy/public/favicon.ico b/packages/app-legacy/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6cfdc82c06de15c0380bb01b21c98d657e5a4631 GIT binary patch literal 9662 zcmeI2TTfL-6vu}E5_s{Fh|r4k!3qW?Tnd8mmPjCozIx-OFYq;L!X>sTnBpaA(l&ij zDA)I15;TFpC(t&rwcetTG<^hArt3H7O!u69_C9BC1ba+6WSyBkd-kmVS~IiOni-VJ zlfSw;CBGl3v5%E1RZ3M0NtOCki2AUkwN_zt;J|_Ay1KgYW5G0vhePKGMUdC%0 zdE49D^`1R@-YMQp`T;D zs_GNQ2fgPac2@?*0jwfJos~h>%&)AKT$(c$vp)wTSOxQa@QbZ!VN6)7{ zHc8gQ1oD)XmAyCkv58<7?4#l*md~6y6WK-<78dHgd-q0?b?45V8rtk}#J1ue@NsQz zZL23wp3KUFbv-gNqE}W{WZG%{{P}Zz{`~pC@30jU6BDK#KjY)$n{F4dlQt&6=VjB; z_3PISo_qK1>1)@n>8DSh>NjuR=o2SSq+4HvmyKtp_Yxl% zJH$L9Cdkxw+qP|bb#+zWy?a;Z<>i^Sm6esIZftBU?c4eJc_SZfp=ZyYY3efZi2DxA zV2^^oqN2j+aA;^K1;djkPxRvAVoDu2Mn^~Wt5>h|rAwFe<;#~%`Ocj?Y3)4BJ2Jcr zj$7j3M~5$7ywHyyKi0?s-wPKm7@2O~yqU(ky1H5~FE5+-{7@DjnbuCJ0;&5}=%vv1 zfcL@J;U5P-?Jr)uXy!Zf2svz;nwrws15cL;o~5Owy0^F25M}@S1orQw{x6|#0^WyX z-w1xb!A@aI78Vxtty{PB6&wic6{=MYs@@M`&zW#&yL+@Z;TU!gT z^M!)z10j5c8-Ih<>YY43cCf^qKSB6yU8;`)^H)ND2r)Mx%3lic?(MN}HhwFIQZ0h- zp%9pV78*#>FUh*4WWI5|TV9)~|3sf33O2Am66z3g=R4(2p+AL?fpPG^kM|rGKQTdn zf4`oao71;%-zN6+VPMXn_jX^siwhZO!(8&}v90t;-}vb$dyu&Jt*ybAzJC2$FD)(U z-Me=uaj*s-3H_9U%f;xGnM)|dNSEPl@WYFgjz3C_hK%4xW@Y>v zeOdhI2>j^G)wjPK2S4A^FUg%ho?o@|=f{jZ6Bw{d%pbl{X3xs}AzrfchuGVX*Z&+B zKkIjJaL~k&#A503%!u$ruWcE4W0z>dTKDDz@e+L!qY`_=%Y7eIj*CC2kJLJAKhbTF z4A>>sI5zg(+s{<2p6*iXUH`>gvi=LZgx%x=xnSG1(Y3#Sc z_=MH-4GFtOEKZ;Fef;?GNPWe{&wh*jI(lQ@XcPQ{bck=pKR}+J_Qrs3tWBmb`lN4o zgq>@+x`>0{?jPZ?Z{I$nANFIUp#3AZ)!jc5Yoy0Z%IKXw&<)r)6X0Bfvl)20c>U!# z`0f6Ecz8Ia&usg5;*9R@ZsVJX&-@tJ`?D8y>)}Dlc19Kle^F79IV+i+o&8rYj01aN z=B&$~a@6w^;xx{~?3n;M=ZuCr&TRbsy3ZCrXVFp4uWsD9Vf2Cw=m_11onPUnNz8BJ zF%q%2%bzl5X!OAt&?neQob&nH`k%oJb~%GvV_)un?|&vH=L`;=lQ>VY^VZ)M|L?9% z>{M7xz&fHY5`DuXO8gJz zR_@p>esZ}RzRH;7>isF$!3M@dhYo$=Vz=dnhK3p5BV#VjCFTs6z{VZB#T%5%IDF3d zupU^`*rosC{|Dd+Z`LYUz{DMU5N3ZJ_mZ-vA0RK^a=(Yaz~|w&x02uiFL=Tm49(5W zzwh6_ztxX3+cUOR{KKTg + + + + + + + <% if (config.getOptional('app.branding.iconLogo')) { %> + + <% } else { %> + + + + + + + + + <% } %> + + <%= config.getOptionalString('app.title') ?? 'Backstage' %> + + + +
+ + diff --git a/packages/app/public/learning-paths/data.json b/packages/app-legacy/public/learning-paths/data.json similarity index 100% rename from packages/app/public/learning-paths/data.json rename to packages/app-legacy/public/learning-paths/data.json diff --git a/packages/app/public/manifest.json b/packages/app-legacy/public/manifest.json similarity index 100% rename from packages/app/public/manifest.json rename to packages/app-legacy/public/manifest.json diff --git a/packages/app-legacy/public/robots.txt b/packages/app-legacy/public/robots.txt new file mode 100644 index 0000000000..01b0f9a107 --- /dev/null +++ b/packages/app-legacy/public/robots.txt @@ -0,0 +1,2 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * diff --git a/packages/app/public/safari-pinned-tab.svg b/packages/app-legacy/public/safari-pinned-tab.svg similarity index 100% rename from packages/app/public/safari-pinned-tab.svg rename to packages/app-legacy/public/safari-pinned-tab.svg diff --git a/packages/app-legacy/src/App.test.tsx b/packages/app-legacy/src/App.test.tsx new file mode 100644 index 0000000000..99089718a6 --- /dev/null +++ b/packages/app-legacy/src/App.test.tsx @@ -0,0 +1,43 @@ +import { lazy, Suspense } from 'react'; + +import { removeScalprum } from '@scalprum/core'; +import { mockPluginData } from '@scalprum/react-test-utils'; +import { render, waitFor } from '@testing-library/react'; + +import TestRoot from './utils/test/TestRoot'; + +const AppBase = lazy(() => import('./components/AppBase')); + +describe('App', () => { + beforeEach(() => { + removeScalprum(); + }); + it('should render', async () => { + const { TestScalprumProvider } = mockPluginData({}, {}); + process.env = { + NODE_ENV: 'test', + APP_CONFIG: [ + { + data: { + app: { title: 'Test' }, + backend: { baseUrl: 'http://localhost:7007' }, + auth: { environment: 'development' }, + }, + context: 'test', + }, + ] as any, + }; + + const rendered = render( + + + + + + + , + ); + + await waitFor(() => expect(rendered.baseElement).toBeInTheDocument()); + }, 100000); +}); diff --git a/packages/app-legacy/src/App.tsx b/packages/app-legacy/src/App.tsx new file mode 100644 index 0000000000..6be8d0c837 --- /dev/null +++ b/packages/app-legacy/src/App.tsx @@ -0,0 +1,31 @@ +import GlobalStyles from '@mui/material/GlobalStyles'; + +import { apis } from './apis'; +import ScalprumRoot from './components/DynamicRoot/ScalprumRoot'; +import { DefaultMainMenuItems } from './consts'; + +// The base UI configuration, these values can be overridden by values +// specified in external configuration files +const baseFrontendConfig = { + context: 'frontend', + data: { + dynamicPlugins: { + frontend: { + 'default.main-menu-items': DefaultMainMenuItems, + }, + }, + }, +}; + +const AppRoot = () => ( + <> + + import('./components/AppBase')} + baseFrontendConfig={baseFrontendConfig} + /> + +); + +export default AppRoot; diff --git a/packages/app/src/api/AuthApiRefs.ts b/packages/app-legacy/src/api/AuthApiRefs.ts similarity index 100% rename from packages/app/src/api/AuthApiRefs.ts rename to packages/app-legacy/src/api/AuthApiRefs.ts diff --git a/packages/app/src/api/LearningPathApiClient.ts b/packages/app-legacy/src/api/LearningPathApiClient.ts similarity index 100% rename from packages/app/src/api/LearningPathApiClient.ts rename to packages/app-legacy/src/api/LearningPathApiClient.ts diff --git a/packages/app/src/api/index.ts b/packages/app-legacy/src/api/index.ts similarity index 100% rename from packages/app/src/api/index.ts rename to packages/app-legacy/src/api/index.ts diff --git a/packages/app/src/apis.ts b/packages/app-legacy/src/apis.ts similarity index 100% rename from packages/app/src/apis.ts rename to packages/app-legacy/src/apis.ts diff --git a/packages/app/src/bootstrap.tsx b/packages/app-legacy/src/bootstrap.tsx similarity index 100% rename from packages/app/src/bootstrap.tsx rename to packages/app-legacy/src/bootstrap.tsx diff --git a/packages/app-legacy/src/build-metadata.json b/packages/app-legacy/src/build-metadata.json new file mode 100644 index 0000000000..47b43bc1f7 --- /dev/null +++ b/packages/app-legacy/src/build-metadata.json @@ -0,0 +1,9 @@ +{ + "title": "RHDH Metadata", + "titleKey": "app.userSettings.infoCard.title", + "card": { + "RHDH Version": "1.11.0", + "Backstage Version": "1.52.0", + "Last Commit": "repo @ 2026-06-22T15:58:40Z" + } +} \ No newline at end of file diff --git a/packages/app/src/common.tsx b/packages/app-legacy/src/common.tsx similarity index 100% rename from packages/app/src/common.tsx rename to packages/app-legacy/src/common.tsx diff --git a/packages/app/src/components/AppBase/AppBase.tsx b/packages/app-legacy/src/components/AppBase/AppBase.tsx similarity index 100% rename from packages/app/src/components/AppBase/AppBase.tsx rename to packages/app-legacy/src/components/AppBase/AppBase.tsx diff --git a/packages/app/src/components/AppBase/index.ts b/packages/app-legacy/src/components/AppBase/index.ts similarity index 100% rename from packages/app/src/components/AppBase/index.ts rename to packages/app-legacy/src/components/AppBase/index.ts diff --git a/packages/app/src/components/CustomIcons/DeveloperHub.tsx b/packages/app-legacy/src/components/CustomIcons/DeveloperHub.tsx similarity index 100% rename from packages/app/src/components/CustomIcons/DeveloperHub.tsx rename to packages/app-legacy/src/components/CustomIcons/DeveloperHub.tsx diff --git a/packages/app/src/components/DynamicRoot/CommonIcons.tsx b/packages/app-legacy/src/components/DynamicRoot/CommonIcons.tsx similarity index 100% rename from packages/app/src/components/DynamicRoot/CommonIcons.tsx rename to packages/app-legacy/src/components/DynamicRoot/CommonIcons.tsx diff --git a/packages/app/src/components/DynamicRoot/DynamicRoot.test.tsx b/packages/app-legacy/src/components/DynamicRoot/DynamicRoot.test.tsx similarity index 100% rename from packages/app/src/components/DynamicRoot/DynamicRoot.test.tsx rename to packages/app-legacy/src/components/DynamicRoot/DynamicRoot.test.tsx diff --git a/packages/app/src/components/DynamicRoot/DynamicRoot.tsx b/packages/app-legacy/src/components/DynamicRoot/DynamicRoot.tsx similarity index 100% rename from packages/app/src/components/DynamicRoot/DynamicRoot.tsx rename to packages/app-legacy/src/components/DynamicRoot/DynamicRoot.tsx diff --git a/packages/app/src/components/DynamicRoot/Loader.tsx b/packages/app-legacy/src/components/DynamicRoot/Loader.tsx similarity index 100% rename from packages/app/src/components/DynamicRoot/Loader.tsx rename to packages/app-legacy/src/components/DynamicRoot/Loader.tsx diff --git a/packages/app/src/components/DynamicRoot/ScalprumRoot.tsx b/packages/app-legacy/src/components/DynamicRoot/ScalprumRoot.tsx similarity index 100% rename from packages/app/src/components/DynamicRoot/ScalprumRoot.tsx rename to packages/app-legacy/src/components/DynamicRoot/ScalprumRoot.tsx diff --git a/packages/app/src/components/DynamicRoot/defaultAppComponents.tsx b/packages/app-legacy/src/components/DynamicRoot/defaultAppComponents.tsx similarity index 100% rename from packages/app/src/components/DynamicRoot/defaultAppComponents.tsx rename to packages/app-legacy/src/components/DynamicRoot/defaultAppComponents.tsx diff --git a/packages/app/src/components/DynamicRoot/index.ts b/packages/app-legacy/src/components/DynamicRoot/index.ts similarity index 100% rename from packages/app/src/components/DynamicRoot/index.ts rename to packages/app-legacy/src/components/DynamicRoot/index.ts diff --git a/packages/app/src/components/ErrorPages/ErrorPage.tsx b/packages/app-legacy/src/components/ErrorPages/ErrorPage.tsx similarity index 100% rename from packages/app/src/components/ErrorPages/ErrorPage.tsx rename to packages/app-legacy/src/components/ErrorPages/ErrorPage.tsx diff --git a/packages/app/src/components/ErrorPages/NotFoundErrorPage.tsx b/packages/app-legacy/src/components/ErrorPages/NotFoundErrorPage.tsx similarity index 100% rename from packages/app/src/components/ErrorPages/NotFoundErrorPage.tsx rename to packages/app-legacy/src/components/ErrorPages/NotFoundErrorPage.tsx diff --git a/packages/app/src/components/ErrorPages/errorButtons/ContactSupportButton.tsx b/packages/app-legacy/src/components/ErrorPages/errorButtons/ContactSupportButton.tsx similarity index 100% rename from packages/app/src/components/ErrorPages/errorButtons/ContactSupportButton.tsx rename to packages/app-legacy/src/components/ErrorPages/errorButtons/ContactSupportButton.tsx diff --git a/packages/app/src/components/ErrorPages/errorButtons/GoBackButton.tsx b/packages/app-legacy/src/components/ErrorPages/errorButtons/GoBackButton.tsx similarity index 100% rename from packages/app/src/components/ErrorPages/errorButtons/GoBackButton.tsx rename to packages/app-legacy/src/components/ErrorPages/errorButtons/GoBackButton.tsx diff --git a/packages/app/src/components/ErrorPages/illustrations/collaboration/collaboration-dark.svg b/packages/app-legacy/src/components/ErrorPages/illustrations/collaboration/collaboration-dark.svg similarity index 100% rename from packages/app/src/components/ErrorPages/illustrations/collaboration/collaboration-dark.svg rename to packages/app-legacy/src/components/ErrorPages/illustrations/collaboration/collaboration-dark.svg diff --git a/packages/app/src/components/ErrorPages/illustrations/collaboration/collaboration-light.svg b/packages/app-legacy/src/components/ErrorPages/illustrations/collaboration/collaboration-light.svg similarity index 100% rename from packages/app/src/components/ErrorPages/illustrations/collaboration/collaboration-light.svg rename to packages/app-legacy/src/components/ErrorPages/illustrations/collaboration/collaboration-light.svg diff --git a/packages/app/src/components/ErrorPages/illustrations/collaboration/collaboration.tsx b/packages/app-legacy/src/components/ErrorPages/illustrations/collaboration/collaboration.tsx similarity index 100% rename from packages/app/src/components/ErrorPages/illustrations/collaboration/collaboration.tsx rename to packages/app-legacy/src/components/ErrorPages/illustrations/collaboration/collaboration.tsx diff --git a/packages/app/src/components/ErrorPages/illustrations/illustration.tsx b/packages/app-legacy/src/components/ErrorPages/illustrations/illustration.tsx similarity index 100% rename from packages/app/src/components/ErrorPages/illustrations/illustration.tsx rename to packages/app-legacy/src/components/ErrorPages/illustrations/illustration.tsx diff --git a/packages/app/src/components/Root/ApplicationDrawer.tsx b/packages/app-legacy/src/components/Root/ApplicationDrawer.tsx similarity index 100% rename from packages/app/src/components/Root/ApplicationDrawer.tsx rename to packages/app-legacy/src/components/Root/ApplicationDrawer.tsx diff --git a/packages/app/src/components/Root/ApplicationHeaders.tsx b/packages/app-legacy/src/components/Root/ApplicationHeaders.tsx similarity index 100% rename from packages/app/src/components/Root/ApplicationHeaders.tsx rename to packages/app-legacy/src/components/Root/ApplicationHeaders.tsx diff --git a/packages/app/src/components/Root/ApplicationListener.test.tsx b/packages/app-legacy/src/components/Root/ApplicationListener.test.tsx similarity index 100% rename from packages/app/src/components/Root/ApplicationListener.test.tsx rename to packages/app-legacy/src/components/Root/ApplicationListener.test.tsx diff --git a/packages/app/src/components/Root/ApplicationListener.tsx b/packages/app-legacy/src/components/Root/ApplicationListener.tsx similarity index 100% rename from packages/app/src/components/Root/ApplicationListener.tsx rename to packages/app-legacy/src/components/Root/ApplicationListener.tsx diff --git a/packages/app/src/components/Root/ApplicationProvider.test.tsx b/packages/app-legacy/src/components/Root/ApplicationProvider.test.tsx similarity index 100% rename from packages/app/src/components/Root/ApplicationProvider.test.tsx rename to packages/app-legacy/src/components/Root/ApplicationProvider.test.tsx diff --git a/packages/app/src/components/Root/ApplicationProvider.tsx b/packages/app-legacy/src/components/Root/ApplicationProvider.tsx similarity index 100% rename from packages/app/src/components/Root/ApplicationProvider.tsx rename to packages/app-legacy/src/components/Root/ApplicationProvider.tsx diff --git a/packages/app/src/components/Root/ConfigUpdater.tsx b/packages/app-legacy/src/components/Root/ConfigUpdater.tsx similarity index 100% rename from packages/app/src/components/Root/ConfigUpdater.tsx rename to packages/app-legacy/src/components/Root/ConfigUpdater.tsx diff --git a/packages/app/src/components/Root/CustomSidebarItem.test.tsx b/packages/app-legacy/src/components/Root/CustomSidebarItem.test.tsx similarity index 100% rename from packages/app/src/components/Root/CustomSidebarItem.test.tsx rename to packages/app-legacy/src/components/Root/CustomSidebarItem.test.tsx diff --git a/packages/app/src/components/Root/CustomSidebarItem.tsx b/packages/app-legacy/src/components/Root/CustomSidebarItem.tsx similarity index 100% rename from packages/app/src/components/Root/CustomSidebarItem.tsx rename to packages/app-legacy/src/components/Root/CustomSidebarItem.tsx diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app-legacy/src/components/Root/LogoFull.tsx similarity index 100% rename from packages/app/src/components/Root/LogoFull.tsx rename to packages/app-legacy/src/components/Root/LogoFull.tsx diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app-legacy/src/components/Root/LogoIcon.tsx similarity index 100% rename from packages/app/src/components/Root/LogoIcon.tsx rename to packages/app-legacy/src/components/Root/LogoIcon.tsx diff --git a/packages/app/src/components/Root/MenuIcon.tsx b/packages/app-legacy/src/components/Root/MenuIcon.tsx similarity index 100% rename from packages/app/src/components/Root/MenuIcon.tsx rename to packages/app-legacy/src/components/Root/MenuIcon.tsx diff --git a/packages/app/src/components/Root/ResizableDrawer.tsx b/packages/app-legacy/src/components/Root/ResizableDrawer.tsx similarity index 100% rename from packages/app/src/components/Root/ResizableDrawer.tsx rename to packages/app-legacy/src/components/Root/ResizableDrawer.tsx diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app-legacy/src/components/Root/Root.tsx similarity index 100% rename from packages/app/src/components/Root/Root.tsx rename to packages/app-legacy/src/components/Root/Root.tsx diff --git a/packages/app/src/components/Root/SidebarLogo.test.tsx b/packages/app-legacy/src/components/Root/SidebarLogo.test.tsx similarity index 100% rename from packages/app/src/components/Root/SidebarLogo.test.tsx rename to packages/app-legacy/src/components/Root/SidebarLogo.test.tsx diff --git a/packages/app-next/src/modules/nav/SidebarLogo.tsx b/packages/app-legacy/src/components/Root/SidebarLogo.tsx similarity index 100% rename from packages/app-next/src/modules/nav/SidebarLogo.tsx rename to packages/app-legacy/src/components/Root/SidebarLogo.tsx diff --git a/packages/app/src/components/Root/index.ts b/packages/app-legacy/src/components/Root/index.ts similarity index 100% rename from packages/app/src/components/Root/index.ts rename to packages/app-legacy/src/components/Root/index.ts diff --git a/packages/app/src/components/SignInPage/SignInPage.tsx b/packages/app-legacy/src/components/SignInPage/SignInPage.tsx similarity index 100% rename from packages/app/src/components/SignInPage/SignInPage.tsx rename to packages/app-legacy/src/components/SignInPage/SignInPage.tsx diff --git a/packages/app/src/components/UserSettings/GeneralPage.test.tsx b/packages/app-legacy/src/components/UserSettings/GeneralPage.test.tsx similarity index 100% rename from packages/app/src/components/UserSettings/GeneralPage.test.tsx rename to packages/app-legacy/src/components/UserSettings/GeneralPage.test.tsx diff --git a/packages/app/src/components/UserSettings/GeneralPage.tsx b/packages/app-legacy/src/components/UserSettings/GeneralPage.tsx similarity index 100% rename from packages/app/src/components/UserSettings/GeneralPage.tsx rename to packages/app-legacy/src/components/UserSettings/GeneralPage.tsx diff --git a/packages/app/src/components/UserSettings/InfoCard.test.tsx b/packages/app-legacy/src/components/UserSettings/InfoCard.test.tsx similarity index 100% rename from packages/app/src/components/UserSettings/InfoCard.test.tsx rename to packages/app-legacy/src/components/UserSettings/InfoCard.test.tsx diff --git a/packages/app/src/components/UserSettings/InfoCard.tsx b/packages/app-legacy/src/components/UserSettings/InfoCard.tsx similarity index 100% rename from packages/app/src/components/UserSettings/InfoCard.tsx rename to packages/app-legacy/src/components/UserSettings/InfoCard.tsx diff --git a/packages/app/src/components/UserSettings/SettingsPages.tsx b/packages/app-legacy/src/components/UserSettings/SettingsPages.tsx similarity index 100% rename from packages/app/src/components/UserSettings/SettingsPages.tsx rename to packages/app-legacy/src/components/UserSettings/SettingsPages.tsx diff --git a/packages/app/src/components/catalog/EntityPage/ApiTabContent.tsx b/packages/app-legacy/src/components/catalog/EntityPage/ApiTabContent.tsx similarity index 100% rename from packages/app/src/components/catalog/EntityPage/ApiTabContent.tsx rename to packages/app-legacy/src/components/catalog/EntityPage/ApiTabContent.tsx diff --git a/packages/app/src/components/catalog/EntityPage/ContextMenuAwareEntityLayout.tsx b/packages/app-legacy/src/components/catalog/EntityPage/ContextMenuAwareEntityLayout.tsx similarity index 100% rename from packages/app/src/components/catalog/EntityPage/ContextMenuAwareEntityLayout.tsx rename to packages/app-legacy/src/components/catalog/EntityPage/ContextMenuAwareEntityLayout.tsx diff --git a/packages/app/src/components/catalog/EntityPage/DefinitionTabContent.tsx b/packages/app-legacy/src/components/catalog/EntityPage/DefinitionTabContent.tsx similarity index 100% rename from packages/app/src/components/catalog/EntityPage/DefinitionTabContent.tsx rename to packages/app-legacy/src/components/catalog/EntityPage/DefinitionTabContent.tsx diff --git a/packages/app/src/components/catalog/EntityPage/DependenciesTabContent.tsx b/packages/app-legacy/src/components/catalog/EntityPage/DependenciesTabContent.tsx similarity index 100% rename from packages/app/src/components/catalog/EntityPage/DependenciesTabContent.tsx rename to packages/app-legacy/src/components/catalog/EntityPage/DependenciesTabContent.tsx diff --git a/packages/app/src/components/catalog/EntityPage/DiagramTabContent.tsx b/packages/app-legacy/src/components/catalog/EntityPage/DiagramTabContent.tsx similarity index 100% rename from packages/app/src/components/catalog/EntityPage/DiagramTabContent.tsx rename to packages/app-legacy/src/components/catalog/EntityPage/DiagramTabContent.tsx diff --git a/packages/app/src/components/catalog/EntityPage/DynamicEntityTab.tsx b/packages/app-legacy/src/components/catalog/EntityPage/DynamicEntityTab.tsx similarity index 100% rename from packages/app/src/components/catalog/EntityPage/DynamicEntityTab.tsx rename to packages/app-legacy/src/components/catalog/EntityPage/DynamicEntityTab.tsx diff --git a/packages/app/src/components/catalog/EntityPage/EntityPage.tsx b/packages/app-legacy/src/components/catalog/EntityPage/EntityPage.tsx similarity index 100% rename from packages/app/src/components/catalog/EntityPage/EntityPage.tsx rename to packages/app-legacy/src/components/catalog/EntityPage/EntityPage.tsx diff --git a/packages/app/src/components/catalog/EntityPage/OverviewTabContent.tsx b/packages/app-legacy/src/components/catalog/EntityPage/OverviewTabContent.tsx similarity index 100% rename from packages/app/src/components/catalog/EntityPage/OverviewTabContent.tsx rename to packages/app-legacy/src/components/catalog/EntityPage/OverviewTabContent.tsx diff --git a/packages/app/src/components/catalog/EntityPage/defaultTabs.tsx b/packages/app-legacy/src/components/catalog/EntityPage/defaultTabs.tsx similarity index 100% rename from packages/app/src/components/catalog/EntityPage/defaultTabs.tsx rename to packages/app-legacy/src/components/catalog/EntityPage/defaultTabs.tsx diff --git a/packages/app/src/components/catalog/EntityPage/index.tsx b/packages/app-legacy/src/components/catalog/EntityPage/index.tsx similarity index 100% rename from packages/app/src/components/catalog/EntityPage/index.tsx rename to packages/app-legacy/src/components/catalog/EntityPage/index.tsx diff --git a/packages/app/src/components/catalog/EntityPage/utils.test.tsx b/packages/app-legacy/src/components/catalog/EntityPage/utils.test.tsx similarity index 100% rename from packages/app/src/components/catalog/EntityPage/utils.test.tsx rename to packages/app-legacy/src/components/catalog/EntityPage/utils.test.tsx diff --git a/packages/app/src/components/catalog/EntityPage/utils.tsx b/packages/app-legacy/src/components/catalog/EntityPage/utils.tsx similarity index 100% rename from packages/app/src/components/catalog/EntityPage/utils.tsx rename to packages/app-legacy/src/components/catalog/EntityPage/utils.tsx diff --git a/packages/app/src/components/catalog/Grid/Grid.tsx b/packages/app-legacy/src/components/catalog/Grid/Grid.tsx similarity index 100% rename from packages/app/src/components/catalog/Grid/Grid.tsx rename to packages/app-legacy/src/components/catalog/Grid/Grid.tsx diff --git a/packages/app/src/components/catalog/Grid/index.ts b/packages/app-legacy/src/components/catalog/Grid/index.ts similarity index 100% rename from packages/app/src/components/catalog/Grid/index.ts rename to packages/app-legacy/src/components/catalog/Grid/index.ts diff --git a/packages/app/src/components/catalog/filters/CustomCatalogFilters.tsx b/packages/app-legacy/src/components/catalog/filters/CustomCatalogFilters.tsx similarity index 100% rename from packages/app/src/components/catalog/filters/CustomCatalogFilters.tsx rename to packages/app-legacy/src/components/catalog/filters/CustomCatalogFilters.tsx diff --git a/packages/app/src/components/catalog/filters/CustomEntityTagPicker.test.ts b/packages/app-legacy/src/components/catalog/filters/CustomEntityTagPicker.test.ts similarity index 100% rename from packages/app/src/components/catalog/filters/CustomEntityTagPicker.test.ts rename to packages/app-legacy/src/components/catalog/filters/CustomEntityTagPicker.test.ts diff --git a/packages/app/src/components/catalog/filters/CustomEntityTagPicker.tsx b/packages/app-legacy/src/components/catalog/filters/CustomEntityTagPicker.tsx similarity index 100% rename from packages/app/src/components/catalog/filters/CustomEntityTagPicker.tsx rename to packages/app-legacy/src/components/catalog/filters/CustomEntityTagPicker.tsx diff --git a/packages/app/src/components/catalog/utils.tsx b/packages/app-legacy/src/components/catalog/utils.tsx similarity index 100% rename from packages/app/src/components/catalog/utils.tsx rename to packages/app-legacy/src/components/catalog/utils.tsx diff --git a/packages/app/src/components/learningPaths/LearningPathsPage.test.tsx b/packages/app-legacy/src/components/learningPaths/LearningPathsPage.test.tsx similarity index 100% rename from packages/app/src/components/learningPaths/LearningPathsPage.test.tsx rename to packages/app-legacy/src/components/learningPaths/LearningPathsPage.test.tsx diff --git a/packages/app/src/components/learningPaths/LearningPathsPage.tsx b/packages/app-legacy/src/components/learningPaths/LearningPathsPage.tsx similarity index 100% rename from packages/app/src/components/learningPaths/LearningPathsPage.tsx rename to packages/app-legacy/src/components/learningPaths/LearningPathsPage.tsx diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app-legacy/src/components/search/SearchPage.tsx similarity index 100% rename from packages/app/src/components/search/SearchPage.tsx rename to packages/app-legacy/src/components/search/SearchPage.tsx diff --git a/packages/app/src/consts.ts b/packages/app-legacy/src/consts.ts similarity index 100% rename from packages/app/src/consts.ts rename to packages/app-legacy/src/consts.ts diff --git a/packages/app/src/hooks/useLanguagePreference.test.ts b/packages/app-legacy/src/hooks/useLanguagePreference.test.ts similarity index 100% rename from packages/app/src/hooks/useLanguagePreference.test.ts rename to packages/app-legacy/src/hooks/useLanguagePreference.test.ts diff --git a/packages/app/src/hooks/useLanguagePreference.ts b/packages/app-legacy/src/hooks/useLanguagePreference.ts similarity index 100% rename from packages/app/src/hooks/useLanguagePreference.ts rename to packages/app-legacy/src/hooks/useLanguagePreference.ts diff --git a/packages/app/src/hooks/useLearningPathData.test.ts b/packages/app-legacy/src/hooks/useLearningPathData.test.ts similarity index 100% rename from packages/app/src/hooks/useLearningPathData.test.ts rename to packages/app-legacy/src/hooks/useLearningPathData.test.ts diff --git a/packages/app/src/hooks/useLearningPathData.ts b/packages/app-legacy/src/hooks/useLearningPathData.ts similarity index 100% rename from packages/app/src/hooks/useLearningPathData.ts rename to packages/app-legacy/src/hooks/useLearningPathData.ts diff --git a/packages/app-next/src/hooks/useThemedConfig.test.tsx b/packages/app-legacy/src/hooks/useThemedConfig.test.tsx similarity index 52% rename from packages/app-next/src/hooks/useThemedConfig.test.tsx rename to packages/app-legacy/src/hooks/useThemedConfig.test.tsx index 62eadc08f0..4cddc7e69f 100644 --- a/packages/app-next/src/hooks/useThemedConfig.test.tsx +++ b/packages/app-legacy/src/hooks/useThemedConfig.test.tsx @@ -14,6 +14,8 @@ import { renderHook } from '@testing-library/react'; import { useAppBarBackgroundScheme, useAppBarThemedConfig, + useSidebarSelectedBackgroundColor, + useSystemThemedConfig, } from './useThemedConfig'; const themeWith = (rhdhGeneral?: object) => @@ -25,6 +27,22 @@ const themeWith = (rhdhGeneral?: object) => : {}, ); +const themeWrapper = + (rhdhGeneral?: object) => + ({ children }: PropsWithChildren) => ( + {children} + ); + +const configWrapper = + (brandingData: object) => + ({ children }: PropsWithChildren) => ( + + {children} + + ); + const makeWrapper = (rhdhGeneral: object | undefined, brandingData: object) => { const theme = themeWith(rhdhGeneral); const configApi = mockApis.config({ data: brandingData }); @@ -91,3 +109,74 @@ describe('useAppBarThemedConfig', () => { expect(result.current).toEqual('logo-dark.svg'); }); }); + +describe('useSidebarSelectedBackgroundColor', () => { + it('returns the sidebar selected background color from the theme', () => { + const { result } = renderHook(() => useSidebarSelectedBackgroundColor(), { + wrapper: themeWrapper({ sidebarItemSelectedBackgroundColor: '#abcdef' }), + }); + + expect(result.current).toEqual('#abcdef'); + }); + + it("defaults to '' when the theme does not set the color", () => { + const { result } = renderHook(() => useSidebarSelectedBackgroundColor(), { + wrapper: themeWrapper(), + }); + + expect(result.current).toEqual(''); + }); +}); + +describe('useSystemThemedConfig', () => { + const originalMatchMedia = window.matchMedia; + + const mockPrefersDark = (prefersDark: boolean) => { + window.matchMedia = jest.fn().mockImplementation((query: string) => ({ + matches: prefersDark, + media: query, + onchange: null, + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + addListener: jest.fn(), + removeListener: jest.fn(), + dispatchEvent: jest.fn(), + })); + }; + + afterEach(() => { + window.matchMedia = originalMatchMedia; + }); + + it('selects the branding variant matching the system color scheme', () => { + mockPrefersDark(true); + + const { result } = renderHook( + () => useSystemThemedConfig('app.branding.fullLogo'), + { + wrapper: configWrapper({ + app: { + branding: { + fullLogo: { light: 'logo-light.svg', dark: 'logo-dark.svg' }, + }, + }, + }), + }, + ); + + expect(result.current).toEqual('logo-dark.svg'); + }); + + it('returns a string branding asset unchanged regardless of scheme', () => { + mockPrefersDark(false); + + const { result } = renderHook( + () => useSystemThemedConfig('app.branding.fullLogo'), + { + wrapper: configWrapper({ app: { branding: { fullLogo: 'logo.svg' } } }), + }, + ); + + expect(result.current).toEqual('logo.svg'); + }); +}); diff --git a/packages/app-legacy/src/hooks/useThemedConfig.ts b/packages/app-legacy/src/hooks/useThemedConfig.ts new file mode 100644 index 0000000000..daedf86983 --- /dev/null +++ b/packages/app-legacy/src/hooks/useThemedConfig.ts @@ -0,0 +1,84 @@ +import { useEffect, useState } from 'react'; + +import { configApiRef, useApi } from '@backstage/core-plugin-api'; + +import { useTheme } from '@mui/material/styles'; +import type { ThemeConfig } from '@red-hat-developer-hub/backstage-plugin-theme'; + +import type { Config } from '../../config'; + +type fullLogoType = NonNullable['fullLogo']; +type iconLogoType = NonNullable['fullLogo']; + +/** + * Get the app bar background scheme from the theme. Defaults to 'dark' if not set. + */ +export const useAppBarBackgroundScheme = () => { + const theme = useTheme(); + + return ( + (theme as ThemeConfig)?.palette?.rhdh?.general?.appBarBackgroundScheme ?? + 'dark' + ); +}; + +/** + * Gets a config value based on the value of `theme.palette.rhdh.general.appBarBackgroundScheme`. + */ +export const useAppBarThemedConfig = ( + key: 'app.branding.fullLogo' | 'app.branding.iconLogo', +) => { + const appBarBackgroundScheme = useAppBarBackgroundScheme(); + + const configApi = useApi(configApiRef); + + /** The fullLogo config specified by Red Hat Developer Hub */ + const fullLogo = configApi.getOptional(key); + + return typeof fullLogo === 'string' + ? fullLogo + : fullLogo?.[appBarBackgroundScheme]; +}; + +/** + * Gets a config value based on the user's system theme. + */ +export const useSystemThemedConfig = ( + key: 'app.branding.fullLogo' | 'app.branding.iconLogo', +) => { + const configApi = useApi(configApiRef); + + /** The fullLogo config specified by Red Hat Developer Hub */ + const fullLogo = configApi.getOptional(key); + + const [colorScheme, setColorScheme] = useState<'light' | 'dark'>('light'); + + useEffect(() => { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const handleChange = (event: MediaQueryListEvent) => { + setColorScheme(event.matches ? 'dark' : 'light'); + }; + + mediaQuery.addEventListener('change', handleChange); + setColorScheme(mediaQuery.matches ? 'dark' : 'light'); + + return () => { + mediaQuery.removeEventListener('change', handleChange); + }; + }, []); + + return typeof fullLogo === 'string' ? fullLogo : fullLogo?.[colorScheme]; +}; + +/** + * Gets the sidebar selected background color from the theme palette. + * Uses theme.palette.rhdh.general.sidebarItemSelectedBackgroundColor. + */ +export const useSidebarSelectedBackgroundColor = () => { + const theme = useTheme(); + + return ( + (theme as ThemeConfig)?.palette?.rhdh?.general + ?.sidebarItemSelectedBackgroundColor ?? '' + ); +}; diff --git a/packages/app-legacy/src/hooks/useTranslation.ts b/packages/app-legacy/src/hooks/useTranslation.ts new file mode 100644 index 0000000000..09eb4ea858 --- /dev/null +++ b/packages/app-legacy/src/hooks/useTranslation.ts @@ -0,0 +1,26 @@ +/* + * 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 { + TranslationFunction, + useTranslationRef, +} from '@backstage/core-plugin-api/alpha'; + +import { rhdhTranslationRef } from '../translations/rhdh/ref'; + +export const useTranslation = (): { + t: TranslationFunction; +} => useTranslationRef(rhdhTranslationRef); diff --git a/packages/app-legacy/src/index.tsx b/packages/app-legacy/src/index.tsx new file mode 100644 index 0000000000..b93c7a0268 --- /dev/null +++ b/packages/app-legacy/src/index.tsx @@ -0,0 +1 @@ +import('./bootstrap'); diff --git a/packages/app-legacy/src/setupTests.ts b/packages/app-legacy/src/setupTests.ts new file mode 100644 index 0000000000..7b0828bfa8 --- /dev/null +++ b/packages/app-legacy/src/setupTests.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/packages/app-next/src/translations/catalog-import/catalog-import-en.ts b/packages/app-legacy/src/translations/catalog-import/catalog-import-en.ts similarity index 100% rename from packages/app-next/src/translations/catalog-import/catalog-import-en.ts rename to packages/app-legacy/src/translations/catalog-import/catalog-import-en.ts diff --git a/packages/app-next/src/translations/catalog-import/catalog-import.ts b/packages/app-legacy/src/translations/catalog-import/catalog-import.ts similarity index 100% rename from packages/app-next/src/translations/catalog-import/catalog-import.ts rename to packages/app-legacy/src/translations/catalog-import/catalog-import.ts diff --git a/packages/app-next/src/translations/catalog-import/de.ts b/packages/app-legacy/src/translations/catalog-import/de.ts similarity index 100% rename from packages/app-next/src/translations/catalog-import/de.ts rename to packages/app-legacy/src/translations/catalog-import/de.ts diff --git a/packages/app-next/src/translations/catalog-import/es.ts b/packages/app-legacy/src/translations/catalog-import/es.ts similarity index 100% rename from packages/app-next/src/translations/catalog-import/es.ts rename to packages/app-legacy/src/translations/catalog-import/es.ts diff --git a/packages/app-next/src/translations/catalog-import/fr.ts b/packages/app-legacy/src/translations/catalog-import/fr.ts similarity index 100% rename from packages/app-next/src/translations/catalog-import/fr.ts rename to packages/app-legacy/src/translations/catalog-import/fr.ts diff --git a/packages/app-next/src/translations/catalog-import/it.ts b/packages/app-legacy/src/translations/catalog-import/it.ts similarity index 100% rename from packages/app-next/src/translations/catalog-import/it.ts rename to packages/app-legacy/src/translations/catalog-import/it.ts diff --git a/packages/app-next/src/translations/catalog-import/ja.ts b/packages/app-legacy/src/translations/catalog-import/ja.ts similarity index 100% rename from packages/app-next/src/translations/catalog-import/ja.ts rename to packages/app-legacy/src/translations/catalog-import/ja.ts diff --git a/packages/app-next/src/translations/catalog/catalog-en.ts b/packages/app-legacy/src/translations/catalog/catalog-en.ts similarity index 100% rename from packages/app-next/src/translations/catalog/catalog-en.ts rename to packages/app-legacy/src/translations/catalog/catalog-en.ts diff --git a/packages/app-next/src/translations/catalog/catalog.ts b/packages/app-legacy/src/translations/catalog/catalog.ts similarity index 100% rename from packages/app-next/src/translations/catalog/catalog.ts rename to packages/app-legacy/src/translations/catalog/catalog.ts diff --git a/packages/app-next/src/translations/catalog/de.ts b/packages/app-legacy/src/translations/catalog/de.ts similarity index 100% rename from packages/app-next/src/translations/catalog/de.ts rename to packages/app-legacy/src/translations/catalog/de.ts diff --git a/packages/app-next/src/translations/catalog/es.ts b/packages/app-legacy/src/translations/catalog/es.ts similarity index 100% rename from packages/app-next/src/translations/catalog/es.ts rename to packages/app-legacy/src/translations/catalog/es.ts diff --git a/packages/app-next/src/translations/catalog/fr.ts b/packages/app-legacy/src/translations/catalog/fr.ts similarity index 100% rename from packages/app-next/src/translations/catalog/fr.ts rename to packages/app-legacy/src/translations/catalog/fr.ts diff --git a/packages/app-next/src/translations/catalog/it.ts b/packages/app-legacy/src/translations/catalog/it.ts similarity index 100% rename from packages/app-next/src/translations/catalog/it.ts rename to packages/app-legacy/src/translations/catalog/it.ts diff --git a/packages/app-next/src/translations/catalog/ja.ts b/packages/app-legacy/src/translations/catalog/ja.ts similarity index 100% rename from packages/app-next/src/translations/catalog/ja.ts rename to packages/app-legacy/src/translations/catalog/ja.ts diff --git a/packages/app-next/src/translations/core-components/core-components-en.ts b/packages/app-legacy/src/translations/core-components/core-components-en.ts similarity index 100% rename from packages/app-next/src/translations/core-components/core-components-en.ts rename to packages/app-legacy/src/translations/core-components/core-components-en.ts diff --git a/packages/app-next/src/translations/core-components/core-components.ts b/packages/app-legacy/src/translations/core-components/core-components.ts similarity index 100% rename from packages/app-next/src/translations/core-components/core-components.ts rename to packages/app-legacy/src/translations/core-components/core-components.ts diff --git a/packages/app-next/src/translations/core-components/de.ts b/packages/app-legacy/src/translations/core-components/de.ts similarity index 100% rename from packages/app-next/src/translations/core-components/de.ts rename to packages/app-legacy/src/translations/core-components/de.ts diff --git a/packages/app-next/src/translations/core-components/es.ts b/packages/app-legacy/src/translations/core-components/es.ts similarity index 100% rename from packages/app-next/src/translations/core-components/es.ts rename to packages/app-legacy/src/translations/core-components/es.ts diff --git a/packages/app-next/src/translations/core-components/fr.ts b/packages/app-legacy/src/translations/core-components/fr.ts similarity index 100% rename from packages/app-next/src/translations/core-components/fr.ts rename to packages/app-legacy/src/translations/core-components/fr.ts diff --git a/packages/app-next/src/translations/core-components/it.ts b/packages/app-legacy/src/translations/core-components/it.ts similarity index 100% rename from packages/app-next/src/translations/core-components/it.ts rename to packages/app-legacy/src/translations/core-components/it.ts diff --git a/packages/app-next/src/translations/core-components/ja.ts b/packages/app-legacy/src/translations/core-components/ja.ts similarity index 100% rename from packages/app-next/src/translations/core-components/ja.ts rename to packages/app-legacy/src/translations/core-components/ja.ts diff --git a/packages/app-next/src/translations/rhdh/de.ts b/packages/app-legacy/src/translations/rhdh/de.ts similarity index 100% rename from packages/app-next/src/translations/rhdh/de.ts rename to packages/app-legacy/src/translations/rhdh/de.ts diff --git a/packages/app-next/src/translations/rhdh/es.ts b/packages/app-legacy/src/translations/rhdh/es.ts similarity index 100% rename from packages/app-next/src/translations/rhdh/es.ts rename to packages/app-legacy/src/translations/rhdh/es.ts diff --git a/packages/app-next/src/translations/rhdh/fr.ts b/packages/app-legacy/src/translations/rhdh/fr.ts similarity index 100% rename from packages/app-next/src/translations/rhdh/fr.ts rename to packages/app-legacy/src/translations/rhdh/fr.ts diff --git a/packages/app-next/src/translations/rhdh/index.ts b/packages/app-legacy/src/translations/rhdh/index.ts similarity index 100% rename from packages/app-next/src/translations/rhdh/index.ts rename to packages/app-legacy/src/translations/rhdh/index.ts diff --git a/packages/app-next/src/translations/rhdh/it.ts b/packages/app-legacy/src/translations/rhdh/it.ts similarity index 100% rename from packages/app-next/src/translations/rhdh/it.ts rename to packages/app-legacy/src/translations/rhdh/it.ts diff --git a/packages/app-next/src/translations/rhdh/ja.ts b/packages/app-legacy/src/translations/rhdh/ja.ts similarity index 100% rename from packages/app-next/src/translations/rhdh/ja.ts rename to packages/app-legacy/src/translations/rhdh/ja.ts diff --git a/packages/app-next/src/translations/rhdh/ref.ts b/packages/app-legacy/src/translations/rhdh/ref.ts similarity index 100% rename from packages/app-next/src/translations/rhdh/ref.ts rename to packages/app-legacy/src/translations/rhdh/ref.ts diff --git a/packages/app-next/src/translations/scaffolder/de.ts b/packages/app-legacy/src/translations/scaffolder/de.ts similarity index 100% rename from packages/app-next/src/translations/scaffolder/de.ts rename to packages/app-legacy/src/translations/scaffolder/de.ts diff --git a/packages/app-next/src/translations/scaffolder/es.ts b/packages/app-legacy/src/translations/scaffolder/es.ts similarity index 100% rename from packages/app-next/src/translations/scaffolder/es.ts rename to packages/app-legacy/src/translations/scaffolder/es.ts diff --git a/packages/app-next/src/translations/scaffolder/fr.ts b/packages/app-legacy/src/translations/scaffolder/fr.ts similarity index 100% rename from packages/app-next/src/translations/scaffolder/fr.ts rename to packages/app-legacy/src/translations/scaffolder/fr.ts diff --git a/packages/app-next/src/translations/scaffolder/it.ts b/packages/app-legacy/src/translations/scaffolder/it.ts similarity index 100% rename from packages/app-next/src/translations/scaffolder/it.ts rename to packages/app-legacy/src/translations/scaffolder/it.ts diff --git a/packages/app-next/src/translations/scaffolder/ja.ts b/packages/app-legacy/src/translations/scaffolder/ja.ts similarity index 100% rename from packages/app-next/src/translations/scaffolder/ja.ts rename to packages/app-legacy/src/translations/scaffolder/ja.ts diff --git a/packages/app-next/src/translations/scaffolder/scaffolder-en.ts b/packages/app-legacy/src/translations/scaffolder/scaffolder-en.ts similarity index 100% rename from packages/app-next/src/translations/scaffolder/scaffolder-en.ts rename to packages/app-legacy/src/translations/scaffolder/scaffolder-en.ts diff --git a/packages/app-next/src/translations/scaffolder/scaffolder.ts b/packages/app-legacy/src/translations/scaffolder/scaffolder.ts similarity index 100% rename from packages/app-next/src/translations/scaffolder/scaffolder.ts rename to packages/app-legacy/src/translations/scaffolder/scaffolder.ts diff --git a/packages/app-next/src/translations/search/ja.ts b/packages/app-legacy/src/translations/search/ja.ts similarity index 100% rename from packages/app-next/src/translations/search/ja.ts rename to packages/app-legacy/src/translations/search/ja.ts diff --git a/packages/app-next/src/translations/search/search-de.ts b/packages/app-legacy/src/translations/search/search-de.ts similarity index 100% rename from packages/app-next/src/translations/search/search-de.ts rename to packages/app-legacy/src/translations/search/search-de.ts diff --git a/packages/app-next/src/translations/search/search-en.ts b/packages/app-legacy/src/translations/search/search-en.ts similarity index 100% rename from packages/app-next/src/translations/search/search-en.ts rename to packages/app-legacy/src/translations/search/search-en.ts diff --git a/packages/app-next/src/translations/search/search-es.ts b/packages/app-legacy/src/translations/search/search-es.ts similarity index 100% rename from packages/app-next/src/translations/search/search-es.ts rename to packages/app-legacy/src/translations/search/search-es.ts diff --git a/packages/app-next/src/translations/search/search-fr.ts b/packages/app-legacy/src/translations/search/search-fr.ts similarity index 100% rename from packages/app-next/src/translations/search/search-fr.ts rename to packages/app-legacy/src/translations/search/search-fr.ts diff --git a/packages/app-next/src/translations/search/search-it.ts b/packages/app-legacy/src/translations/search/search-it.ts similarity index 100% rename from packages/app-next/src/translations/search/search-it.ts rename to packages/app-legacy/src/translations/search/search-it.ts diff --git a/packages/app-next/src/translations/search/search.ts b/packages/app-legacy/src/translations/search/search.ts similarity index 100% rename from packages/app-next/src/translations/search/search.ts rename to packages/app-legacy/src/translations/search/search.ts diff --git a/packages/app-next/src/translations/user-settings/ja.ts b/packages/app-legacy/src/translations/user-settings/ja.ts similarity index 100% rename from packages/app-next/src/translations/user-settings/ja.ts rename to packages/app-legacy/src/translations/user-settings/ja.ts diff --git a/packages/app-next/src/translations/user-settings/user-settings-de.ts b/packages/app-legacy/src/translations/user-settings/user-settings-de.ts similarity index 100% rename from packages/app-next/src/translations/user-settings/user-settings-de.ts rename to packages/app-legacy/src/translations/user-settings/user-settings-de.ts diff --git a/packages/app-next/src/translations/user-settings/user-settings-en.ts b/packages/app-legacy/src/translations/user-settings/user-settings-en.ts similarity index 100% rename from packages/app-next/src/translations/user-settings/user-settings-en.ts rename to packages/app-legacy/src/translations/user-settings/user-settings-en.ts diff --git a/packages/app-next/src/translations/user-settings/user-settings-es.ts b/packages/app-legacy/src/translations/user-settings/user-settings-es.ts similarity index 100% rename from packages/app-next/src/translations/user-settings/user-settings-es.ts rename to packages/app-legacy/src/translations/user-settings/user-settings-es.ts diff --git a/packages/app-next/src/translations/user-settings/user-settings-fr.ts b/packages/app-legacy/src/translations/user-settings/user-settings-fr.ts similarity index 100% rename from packages/app-next/src/translations/user-settings/user-settings-fr.ts rename to packages/app-legacy/src/translations/user-settings/user-settings-fr.ts diff --git a/packages/app-next/src/translations/user-settings/user-settings-it.ts b/packages/app-legacy/src/translations/user-settings/user-settings-it.ts similarity index 100% rename from packages/app-next/src/translations/user-settings/user-settings-it.ts rename to packages/app-legacy/src/translations/user-settings/user-settings-it.ts diff --git a/packages/app-next/src/translations/user-settings/user-settings.ts b/packages/app-legacy/src/translations/user-settings/user-settings.ts similarity index 100% rename from packages/app-next/src/translations/user-settings/user-settings.ts rename to packages/app-legacy/src/translations/user-settings/user-settings.ts diff --git a/packages/app/src/types.d.ts b/packages/app-legacy/src/types.d.ts similarity index 100% rename from packages/app/src/types.d.ts rename to packages/app-legacy/src/types.d.ts diff --git a/packages/app/src/types/types.ts b/packages/app-legacy/src/types/types.ts similarity index 100% rename from packages/app/src/types/types.ts rename to packages/app-legacy/src/types/types.ts diff --git a/packages/app/src/utils/dynamicUI/bindAppRoutes.ts b/packages/app-legacy/src/utils/dynamicUI/bindAppRoutes.ts similarity index 100% rename from packages/app/src/utils/dynamicUI/bindAppRoutes.ts rename to packages/app-legacy/src/utils/dynamicUI/bindAppRoutes.ts diff --git a/packages/app/src/utils/dynamicUI/extractDynamicConfig.test.ts b/packages/app-legacy/src/utils/dynamicUI/extractDynamicConfig.test.ts similarity index 100% rename from packages/app/src/utils/dynamicUI/extractDynamicConfig.test.ts rename to packages/app-legacy/src/utils/dynamicUI/extractDynamicConfig.test.ts diff --git a/packages/app/src/utils/dynamicUI/extractDynamicConfig.ts b/packages/app-legacy/src/utils/dynamicUI/extractDynamicConfig.ts similarity index 100% rename from packages/app/src/utils/dynamicUI/extractDynamicConfig.ts rename to packages/app-legacy/src/utils/dynamicUI/extractDynamicConfig.ts diff --git a/packages/app/src/utils/dynamicUI/extractDynamicConfigFrontend.test.ts b/packages/app-legacy/src/utils/dynamicUI/extractDynamicConfigFrontend.test.ts similarity index 100% rename from packages/app/src/utils/dynamicUI/extractDynamicConfigFrontend.test.ts rename to packages/app-legacy/src/utils/dynamicUI/extractDynamicConfigFrontend.test.ts diff --git a/packages/app/src/utils/dynamicUI/extractDynamicConfigFrontend.ts b/packages/app-legacy/src/utils/dynamicUI/extractDynamicConfigFrontend.ts similarity index 100% rename from packages/app/src/utils/dynamicUI/extractDynamicConfigFrontend.ts rename to packages/app-legacy/src/utils/dynamicUI/extractDynamicConfigFrontend.ts diff --git a/packages/app/src/utils/dynamicUI/getDynamicRootConfig.ts b/packages/app-legacy/src/utils/dynamicUI/getDynamicRootConfig.ts similarity index 100% rename from packages/app/src/utils/dynamicUI/getDynamicRootConfig.ts rename to packages/app-legacy/src/utils/dynamicUI/getDynamicRootConfig.ts diff --git a/packages/app/src/utils/dynamicUI/getMountPointData.test.ts b/packages/app-legacy/src/utils/dynamicUI/getMountPointData.test.ts similarity index 100% rename from packages/app/src/utils/dynamicUI/getMountPointData.test.ts rename to packages/app-legacy/src/utils/dynamicUI/getMountPointData.test.ts diff --git a/packages/app/src/utils/dynamicUI/getMountPointData.ts b/packages/app-legacy/src/utils/dynamicUI/getMountPointData.ts similarity index 100% rename from packages/app/src/utils/dynamicUI/getMountPointData.ts rename to packages/app-legacy/src/utils/dynamicUI/getMountPointData.ts diff --git a/packages/app/src/utils/dynamicUI/initializeRemotePlugins.ts b/packages/app-legacy/src/utils/dynamicUI/initializeRemotePlugins.ts similarity index 100% rename from packages/app/src/utils/dynamicUI/initializeRemotePlugins.ts rename to packages/app-legacy/src/utils/dynamicUI/initializeRemotePlugins.ts diff --git a/packages/app/src/utils/dynamicUI/overrideBaseUrlConfigs.ts b/packages/app-legacy/src/utils/dynamicUI/overrideBaseUrlConfigs.ts similarity index 100% rename from packages/app/src/utils/dynamicUI/overrideBaseUrlConfigs.ts rename to packages/app-legacy/src/utils/dynamicUI/overrideBaseUrlConfigs.ts diff --git a/packages/app/src/utils/language/language.test.ts b/packages/app-legacy/src/utils/language/language.test.ts similarity index 100% rename from packages/app/src/utils/language/language.test.ts rename to packages/app-legacy/src/utils/language/language.test.ts diff --git a/packages/app/src/utils/language/language.ts b/packages/app-legacy/src/utils/language/language.ts similarity index 100% rename from packages/app/src/utils/language/language.ts rename to packages/app-legacy/src/utils/language/language.ts diff --git a/packages/app/src/utils/test/TestRoot.tsx b/packages/app-legacy/src/utils/test/TestRoot.tsx similarity index 100% rename from packages/app/src/utils/test/TestRoot.tsx rename to packages/app-legacy/src/utils/test/TestRoot.tsx diff --git a/packages/app/src/utils/translations/fetchOverrideTranslations.ts b/packages/app-legacy/src/utils/translations/fetchOverrideTranslations.ts similarity index 100% rename from packages/app/src/utils/translations/fetchOverrideTranslations.ts rename to packages/app-legacy/src/utils/translations/fetchOverrideTranslations.ts diff --git a/packages/app/src/utils/translations/index.ts b/packages/app-legacy/src/utils/translations/index.ts similarity index 100% rename from packages/app/src/utils/translations/index.ts rename to packages/app-legacy/src/utils/translations/index.ts diff --git a/packages/app/src/utils/translations/staticTranslationConfigs.ts b/packages/app-legacy/src/utils/translations/staticTranslationConfigs.ts similarity index 100% rename from packages/app/src/utils/translations/staticTranslationConfigs.ts rename to packages/app-legacy/src/utils/translations/staticTranslationConfigs.ts diff --git a/packages/app/src/utils/translations/translationResourceGenerator.test.ts b/packages/app-legacy/src/utils/translations/translationResourceGenerator.test.ts similarity index 100% rename from packages/app/src/utils/translations/translationResourceGenerator.test.ts rename to packages/app-legacy/src/utils/translations/translationResourceGenerator.test.ts diff --git a/packages/app/src/utils/translations/translationResourceGenerator.ts b/packages/app-legacy/src/utils/translations/translationResourceGenerator.ts similarity index 100% rename from packages/app/src/utils/translations/translationResourceGenerator.ts rename to packages/app-legacy/src/utils/translations/translationResourceGenerator.ts diff --git a/packages/app/src/utils/translations/translationResourceProcessor.ts b/packages/app-legacy/src/utils/translations/translationResourceProcessor.ts similarity index 100% rename from packages/app/src/utils/translations/translationResourceProcessor.ts rename to packages/app-legacy/src/utils/translations/translationResourceProcessor.ts diff --git a/packages/app/src/utils/translations/translationUtils.ts b/packages/app-legacy/src/utils/translations/translationUtils.ts similarity index 100% rename from packages/app/src/utils/translations/translationUtils.ts rename to packages/app-legacy/src/utils/translations/translationUtils.ts diff --git a/packages/app-next/tsconfig.json b/packages/app-legacy/tsconfig.json similarity index 76% rename from packages/app-next/tsconfig.json rename to packages/app-legacy/tsconfig.json index e9276c0509..cb92600771 100644 --- a/packages/app-next/tsconfig.json +++ b/packages/app-legacy/tsconfig.json @@ -4,7 +4,7 @@ "exclude": ["node_modules"], "compilerOptions": { "jsx": "preserve", - "outDir": "../../dist-types/packages/app-next", + "outDir": "../../dist-types/packages/app-legacy", "rootDir": "." } } diff --git a/packages/app/turbo.json b/packages/app-legacy/turbo.json similarity index 69% rename from packages/app/turbo.json rename to packages/app-legacy/turbo.json index 55528ef442..4f8b7a09b8 100644 --- a/packages/app/turbo.json +++ b/packages/app-legacy/turbo.json @@ -6,7 +6,7 @@ "persistent": true }, "tsc": { - "outputs": ["../../dist-types/packages/app/**"] + "outputs": ["../../dist-types/packages/app-legacy/**"] } } } diff --git a/packages/app-next/.eslintrc.js b/packages/app-next/.eslintrc.js deleted file mode 100644 index 6f6668b53d..0000000000 --- a/packages/app-next/.eslintrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { - rules: { - '@backstage/no-top-level-material-ui-4-imports': 'error', - }, -}); diff --git a/packages/app-next/config.d.ts b/packages/app-next/config.d.ts deleted file mode 100644 index 7bdb084e80..0000000000 --- a/packages/app-next/config.d.ts +++ /dev/null @@ -1,49 +0,0 @@ -export interface Config { - app: { - branding?: { - /** - * Base64 URI for the full logo. If the value is a string, it is used as the logo for both themes. - * @visibility frontend - */ - // this config is copied to rhdh-plugins/global-header config.d.ts and should be kept in sync - fullLogo?: - | string - | { - /** - * Base64 URI for the logo in light theme - * @visibility frontend - */ - light: string; - /** - * Base64 URI for the logo in dark theme - * @visibility frontend - */ - dark: string; - }; - /** - * size Configuration for the full logo - * The following units are supported: , px, em, rem, - * @visibility frontend - */ - fullLogoWidth?: string | number; - /** - * Base64 URI for the icon logo. If the value is a string, it is used as the logo for both themes. - * @visibility frontend - */ - iconLogo?: - | string - | { - /** - * Base64 URI for the icon logo in light theme - * @visibility frontend - */ - light: string; - /** - * Base64 URI for the icon logo in dark theme - * @visibility frontend - */ - dark: string; - }; - }; - }; -} diff --git a/packages/app-next/public/apple-touch-icon.png b/packages/app-next/public/apple-touch-icon.png deleted file mode 100644 index c4aaa2605f33254234baca95ca3f59980bdbd929..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13575 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

a=JN*Z{I0J+-iNF=DG`UaL;s1ds zuf@8XCsBOj(V#w^n|LJ>+9Vlc1k)A;&q#*nxXTPsP9n(sM<`>E@~tk-&GlLAhcTI< zRGE<96#n@3q)(fzFP0F-a9MUpBt=UkoGX-nGyD(=hF&cU4^#aR)b3U4ikoNyaOy0e z>_8o(Ed|kC>RG@Fh`5^u4&9YUM@P*~%c;#cXTqa54wm?S)=_-tG67+7OpAulxeKfB za?6^477eMebk`!2?6Wd6U%s(=sl)Ne?EV=}!6f0en@a@n`u$czc5@7|+U&dlQ_gJ^ z@ee5t*57G{**B(yFy}*91?iwiZ!qw|^-1~_V*dX(HqNs%&gpZd2_E+#2S8oYtlZo{ zHK5?&0x;<&sbX9YU=XJ|i7kLz^NSOs%dK!j>tQ5kwz~Fl!XdPB7KSd<7(C|Llln-Z z-LEIh4ab7HlU|E}dw)qoP3Pqt@HMRqWR(1om$&(Qdwbit$*{T^=oprWIp3iA4td}p zPsXm$YjzAPXl0;i319BjrNsp)$W|s=G>&JXTk*5h-wNkz&9$|)7gNr=mc?Tu0%U}* z{}Cr&HBAi=Z79Bl0q)En25kC8U>#IGwyrf)(0~im{=`bZ*@}d6z{WB2NOoSP zWKj{&Gsfn%ztT>PzV#I{(_`=Rat^2vr=ETbH4;x02gHfSK)Q}L6G|}$v9#NPeexct zO`t$V$XNpZe?R&ZdmgDg1nS74<{=>b_c|R!?4DK0YT!Egj`jHKIh}1i)c%0fy8UfKLN(O$iAJ6Gz9#mG%em z&N5CqOEcizd5#Vae*d1Gy?O*nMN03yBqiZxOa!tUDHK(~CozDLVlJI+)&YiQsHy3$ z3y{-lwJW+p#O5Z#E|5G_PY*TfU4Nj)h8?gIua3^n8;2L(OsUI88QFKKyk%cM(@lKB5uDFcwSNx-iM99MpMQ~@(0 zY~MqCyobIyIQUm|=(Bz>UFZhP>Oa%?<)eTIz$AOMteobgr={@=ad2>m0UuyWXvz0g zP&h{#|bIum^Db>mq~VsKJd_ a%zCh@;~+<2IZ)XIl9yJNs+KSf`F{XNK|?eE diff --git a/packages/app-next/public/favicon.ico b/packages/app-next/public/favicon.ico deleted file mode 100644 index c023bea83513dcdcfb1148b09e1a7e19de323d25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeI3TWA*B702gGyv2Hn81IEdL{2OyK{0C8YP`g|prWNO#TV&$(FY$@X!M*`1*H!? zRTS+@3sn=P7~3AuhgfT&h#my-(sMM?zSSb76co+L?)vSSmDw|y@0)MFFNq0sU}g5~ z*=w!;T6?X1n=hW1^D4cD22bvF-le&o*XVg(U0v{8?RhVxu3GNA|8#`s{ahNZY3?!igT|$3pZf^bz3|X-+U%vbk z;cef#b*sg@dGltoV#Nxxc=2MhXwjkpC3K(*o%jh18#ZjX0j6T?Si5%ZPs9iB;9at0 ziJ3Zes;Q``Fv2^igidrfHa1#21k+ov6|zCz9|O0&ECCx6ZyuC z8)vRxzi!*{hcb0(IXo6ckdCbHz)TK6Y50kEF8uE5dGX?fdGO#tq>lGjuU<9p-@iAzcki}@ z?bNxvVHtm9A3L((A31WQjrY#ZPBU}nOp9a3jvY3B&YwTu2iN@h^Ua$#Z>-Jyp-f$f zS=UmG0T+e-DU{8IEcnq&t`hIW%=72ZZ5eEBZEcBT5q2IpaKKXX7*r{kF9^L6!UjJs z``W0sM(#yc^C-@(px>C&a%yeLk;i~ZMvJ$)>W%Pj?eTn2Lh*op7y)29m# z*GFKg6{-^gufC&R{azY&Wc3Gr!OcTK@dJKx>h$T; zHpi}AyH;ZUlH0@%xgN#^V*xS;GJjot$>p^16YM&^(l*3jN;ymX7cbwPzZi>J0-}uH z!B3m=u*2V9`17wH{t;jXH)Ucb^gC+~-3ygsPk-P~U%%A}X2{P~#R+!C0M>rRoC7GI zdoKk)dtLU6te?-FJ6H1h8NY*_i#0g1kcsT1{j`f)%USWe`xo|&>}6Dck`EcbnctwK zu^b-VKOhU4?2RbrJJ(R`+_T`<{Uc`%@%&6_uS#|-+R-18IS z9U0oE zlE$8RTVxUMX)&XG?)mL-KR;i(bm_d|{QW}u#2ZfEk7{aaBHxd|uix+ZMmZe6-(iD% zSN)W;=;84F6s%y5Uqki#H8JCSzy3Uq+kNIcHs4q23%>IWq2CX@%jb6&KV|a4{rv{> z!^i&pMk&8TVQiJL>6py(_B)?`Z_@8J193-3Ft76ana{xx_pkfh!^2NK?;ZDA&-*v` ziJsTReYEFwaIch|EcaY^=kfnn=z$iBj(<9M7Qc0I7eA_B)z9j8e%w?iv|6ax-)TuK zR!QIfK>GQY5?^1-ckpg`Hsug9Bu>8(`U2VL5J>SWC%-+JE_#0}?f*4z-n?!Zn|dZr zoM_ihte;9HWFV`)zP=mT@_qa_=n??pRsZ5yu>C=>eLH^q_};yQyiHvT7TbnB1hOS> zzC~Anf!}#lfeGD%F-M~VUHBT^^a*|eTd}s3+YVjmL^p9k-{d><$yZ+v24tcW-9ldz zA8~n=m6c}p?Ad1f_U$(QneP+}_v-3u`&$csZ$Vk##pUFCMkl((58cFQydApl-o0yX z-@a`gK742{U%qU#9oX2j-@A9u5@qV*G73E-3*FKOefB5Vg5BsIHENXo4*2BB6SHvP zLTe{=ckbLVJ9q9hyLRoeW$HqGIVnfa#=>`gPLa>(PSQ_X8Go$Z)22-$XUwx_&&+}a z3#@)@J$Ue-dGzR!CCZ`w_~A!FKP!kKO+Wq39{cg*$5!{#r%&yf2YWR9!(Ozxd4nZ? zUy9A>-X`={Auv#vJcgv{M+bd`&#{~H7O?m+P$6>BRTB_p_N_k@x+k<-ejV>>Y)rxfFEWr7Z$B9N86Qg3kIy)}%I1IirhojOJ^qMEV$&ZV`o?aw zzta4#{RIxFaQ9sV{M*sd zQE>hxzuoy4K739eFmEz;GKV9JcdifA|7q(FFeI%%$jzkn2mOuD;XyZR7}hebZpySn zH*28DlP4Ekf6*85^%vs<;~GR+?c+{cc(5DY;o2bV16_Zy1{|pMr(%lRPQ9)_#Rsc| zKHB=5Jpg+F_9Y)<{~^BdB2fBOr zp9(_vW5lGyZ-4K-rx*tE*rD diff --git a/packages/app-next/public/index.html b/packages/app-next/public/index.html deleted file mode 100644 index ecd3a3275d..0000000000 --- a/packages/app-next/public/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - <%= config.getString('app.title') %> - - - -

- - diff --git a/packages/app-next/public/robots.txt b/packages/app-next/public/robots.txt deleted file mode 100644 index 7d329b1db3..0000000000 --- a/packages/app-next/public/robots.txt +++ /dev/null @@ -1 +0,0 @@ -User-agent: * diff --git a/packages/app-next/src/App.test.tsx b/packages/app-next/src/App.test.tsx deleted file mode 100644 index d00e40d327..0000000000 --- a/packages/app-next/src/App.test.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { renderWithEffects } from '@backstage/test-utils'; - -jest.setTimeout(30_000); - -describe('App', () => { - it('should render', async () => { - process.env = { - NODE_ENV: 'test', - APP_CONFIG: [ - { - data: { - app: { - title: 'Test', - support: { url: 'http://localhost:7007/support' }, - }, - backend: { baseUrl: 'http://localhost:7007' }, - lighthouse: { - baseUrl: 'http://localhost:3003', - }, - techdocs: { - storageUrl: 'http://localhost:7007/api/techdocs/static/docs', - }, - }, - context: 'test', - }, - ] as any, - }; - - const { default: app } = await import('./App'); - const rendered = await renderWithEffects(app); - expect(rendered.baseElement).toBeInTheDocument(); - }); -}); diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx deleted file mode 100644 index d835c5e14f..0000000000 --- a/packages/app-next/src/App.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { createApp } from '@backstage/frontend-defaults'; -import { dynamicFrontendFeaturesLoader } from '@backstage/frontend-dynamic-feature-loader'; -import appVisualizerPlugin from '@backstage/plugin-app-visualizer'; -import catalogPlugin from '@backstage/plugin-catalog/alpha'; -import scaffolderPlugin from '@backstage/plugin-scaffolder/alpha'; -import searchPlugin from '@backstage/plugin-search/alpha'; -import userSettingsPlugin from '@backstage/plugin-user-settings/alpha'; - -import { appAuthModule } from '@red-hat-developer-hub/backstage-plugin-app-auth/alpha'; -import { appIntegrationsModule } from '@red-hat-developer-hub/backstage-plugin-app-integrations/alpha'; -import { appDrawerModule } from '@red-hat-developer-hub/backstage-plugin-app-react/alpha'; -import { rhdhThemeModule } from '@red-hat-developer-hub/backstage-plugin-theme/alpha'; - -import { rhdhApisModule } from './apis/apisModule'; -import { navModule } from './modules/nav'; -import { rhdhTranslationsModule } from './translations/translationsModule'; - -const app = createApp({ - features: [ - // Upstream Backstage plugins - appVisualizerPlugin, - catalogPlugin, - scaffolderPlugin, - searchPlugin, - userSettingsPlugin, - dynamicFrontendFeaturesLoader(), - // RHDH modules (from rhdh-plugins workspace) - appAuthModule, // config-driven sign-in page + OIDC/Keycloak/PingFederate/Auth0/SAML auth APIs - appIntegrationsModule, // SCM integrations + SCM auth APIs - appDrawerModule, // drawer infrastructure (lightspeed, quickstarts, etc.) - // RHDH modules (local to app-next) - navModule, // RHDH-branded sidebar (logo, menu ordering, drawer toggle) - rhdhApisModule, // storage, learning-path, catalog-graph APIs - rhdhTranslationsModule, // RHDH + plugin translation overrides (de, es, fr, it, ja) - rhdhThemeModule, // RHDH light/dark themes - ], -}); - -export default app.createRoot(); diff --git a/packages/app-next/src/hooks/useThemedConfig.ts b/packages/app-next/src/hooks/useThemedConfig.ts deleted file mode 100644 index 0edfec55a3..0000000000 --- a/packages/app-next/src/hooks/useThemedConfig.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { configApiRef, useApi } from '@backstage/core-plugin-api'; - -import { useTheme } from '@mui/material/styles'; -import type { ThemeConfig } from '@red-hat-developer-hub/backstage-plugin-theme'; - -import type { Config } from '../../config'; - -type fullLogoType = NonNullable['fullLogo']; -type iconLogoType = NonNullable['fullLogo']; - -/** - * Get the app bar background scheme from the theme. Defaults to 'dark' if not set. - */ -export const useAppBarBackgroundScheme = () => { - const theme = useTheme(); - - return ( - (theme as ThemeConfig)?.palette?.rhdh?.general?.appBarBackgroundScheme ?? - 'dark' - ); -}; - -/** - * Gets a config value based on the value of `theme.palette.rhdh.general.appBarBackgroundScheme`. - */ -export const useAppBarThemedConfig = ( - key: 'app.branding.fullLogo' | 'app.branding.iconLogo', -) => { - const appBarBackgroundScheme = useAppBarBackgroundScheme(); - - const configApi = useApi(configApiRef); - - /** The fullLogo config specified by Red Hat Developer Hub */ - const fullLogo = configApi.getOptional(key); - - return typeof fullLogo === 'string' - ? fullLogo - : fullLogo?.[appBarBackgroundScheme]; -}; diff --git a/packages/app-next/src/hooks/useTranslation.ts b/packages/app-next/src/hooks/useTranslation.ts deleted file mode 100644 index 3bfd9ba7ec..0000000000 --- a/packages/app-next/src/hooks/useTranslation.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { - TranslationFunction, - useTranslationRef, -} from '@backstage/core-plugin-api/alpha'; - -import { rhdhTranslationRef } from '../translations/rhdh/ref'; - -export const useTranslation = (): { - t: TranslationFunction; -} => useTranslationRef(rhdhTranslationRef); diff --git a/packages/app-next/src/index.tsx b/packages/app-next/src/index.tsx deleted file mode 100644 index ece7196ce0..0000000000 --- a/packages/app-next/src/index.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import '@backstage/cli/asset-types'; -import ReactDOM from 'react-dom/client'; -import app from './App'; -import '@backstage/ui/css/styles.css'; - -ReactDOM.createRoot(document.getElementById('root')!).render(app); diff --git a/packages/app-next/src/setupTests.ts b/packages/app-next/src/setupTests.ts deleted file mode 100644 index 8f2609b7b3..0000000000 --- a/packages/app-next/src/setupTests.ts +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js index 07630f21af..6f6668b53d 100644 --- a/packages/app/.eslintrc.js +++ b/packages/app/.eslintrc.js @@ -1,12 +1,5 @@ -const backstageConfig = require('@backstage/cli/config/eslint-factory')( - __dirname, -); - -module.exports = { - ...backstageConfig, - rules: { - ...backstageConfig.rules, - 'react/react-in-jsx-scope': 'off', - 'react/jsx-uses-react': 'off', - }, -}; +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + rules: { + '@backstage/no-top-level-material-ui-4-imports': 'error', + }, +}); diff --git a/packages/app-next/README.md b/packages/app/README.md similarity index 71% rename from packages/app-next/README.md rename to packages/app/README.md index cb857b50a0..89f281ec6b 100644 --- a/packages/app-next/README.md +++ b/packages/app/README.md @@ -1,18 +1,21 @@ -# app-next +# app ## Overview -This package contains the Red Hat Developer Hub (RHDH) frontend application built using Backstage's new frontend system architecture. The `app-next` package leverages Backstage's new frontend system, which introduces a more modular and extensible architecture compared to the legacy frontend system. This new system is built around the concepts of extensions, plugins, and utility APIs, providing better composability and customization options. +This package contains the Red Hat Developer Hub (RHDH) frontend application built using Backstage's new frontend system architecture. The `app` package leverages Backstage's new frontend system, which introduces a more modular and extensible architecture compared to the legacy frontend system. This new system is built around the concepts of extensions, plugins, and utility APIs, providing better composability and customization options. ## Development ### Primary Development Workflow -The recommended way to run the new frontend system is using the `backend start:next` command: +The recommended way to run the new frontend system is using the root `dev` script or the backend `start` command: ```bash # from the root of the repo -yarn workspace backend start:next +yarn dev + +# or backend only +yarn workspace backend start ``` ### Standalone Development @@ -21,7 +24,7 @@ You can also run the frontend application independently for development: ```bash # from the root of the repo -yarn workspace app-next start +yarn workspace app start ``` ### Other Available Commands diff --git a/packages/app/config.d.ts b/packages/app/config.d.ts old mode 100755 new mode 100644 index 0ddc6b278a..8bab6500ed --- a/packages/app/config.d.ts +++ b/packages/app/config.d.ts @@ -1,17 +1,4 @@ export interface Config { - /** Configurations for the backstage(janus) instance */ - developerHub?: { - /** - * The url of json data for customization. - * @visibility frontend - */ - proxyPath?: string; - /** - * Name of the Backstage flavor (e.g. backstage, rhdh, rhtap) - * @visibility frontend - */ - flavor?: string; - }; app: { branding?: { /** @@ -57,226 +44,8 @@ export interface Config { */ dark: string; }; - /** - * @deepVisibility frontend - */ - theme?: { - [key: string]: unknown; - }; - }; - sidebar?: { - /** - * Show the logo in the sidebar - * @visibility frontend - */ - logo?: boolean; - /** - * Show the search in the sidebar - * @visibility frontend - */ - search?: boolean; - /** - * Show the settings in the sidebar - * @visibility frontend - */ - settings?: boolean; - /** - * Show the administration in the sidebar - * @visibility frontend - */ - administration?: boolean; - }; - quickstart?: Array; - /** - * Optional icon for quickstart. - * @visibility frontend - */ - icon?: string; - /** - * The description of quickstart. - * @visibility frontend - */ - description: string; - /** - * Optional translation key for the description. - * @visibility frontend - */ - descriptionKey?: string; - /** - * Optional action item for quickstart. - * @visibility frontend - */ - cta?: { - /** - * Action item text. - * @visibility frontend - */ - text: string; - /** - * Optional translation key for the action text. - * @visibility frontend - */ - textKey?: string; - /** - * Action item link. - * @visibility frontend - */ - link: string; - }; - }>; - }; - /** @deepVisibility frontend */ - dynamicPlugins: { - /** @deepVisibility frontend */ - frontend?: { - [key: string]: { - dynamicRoutes?: { - path: string; - module?: string; - importName?: string; - menuItem?: { - icon: string; - text: string; - enabled?: boolean; - }; - config: { - props?: { - [key: string]: string; - }; - }; - }[]; - routeBindings?: { - targets?: { - module?: string; - importName: string; - name?: string; - }[]; - bindings?: { - bindTarget: string; - bindMap: { - [key: string]: string; - }; - }[]; - }; - entityTabs?: { - path: string; - title: string; - mountPoint: string; - priority?: number; - }[]; - mountPoints?: { - mountPoint: string; - module?: string; - importName?: string; - config: { - layout?: { - [key: string]: - | string - | { - [key: string]: string; - }; - }; - props?: { - [key: string]: string; - }; - if?: { - allOf?: ( - | { - [key: string]: string | string[]; - } - | string - )[]; - anyOf?: ( - | { - [key: string]: string | string[]; - } - | string - )[]; - oneOf?: ( - | { - [key: string]: string | string[]; - } - | string - )[]; - }; - id?: string; - }; - }[]; - appIcons?: { - module?: string; - importName?: string; - name: string; - }[]; - apiFactories?: { - module?: string; - importName?: string; - }[]; - providerSettings?: { - title: string; - description: string; - provider: string; - }[]; - scaffolderFieldExtensions?: { - module?: string; - importName?: string; - }[]; - signInPage?: { - module?: string; - importName: string; - }; - techdocsAddons?: { - module?: string; - importName?: string; - config?: { - props?: { - [key: string]: string; - }; - }; - }[]; - themes?: { - module?: string; - id: string; - title: string; - variant: 'light' | 'dark'; - icon: string; - importName?: string; - }[]; - translationResources?: { - module?: string; - importName: string; - ref?: string; - }[]; - }; }; }; - /** - * The signInPage provider - * @visibility frontend - */ - signInPage?: string | string[]; - /** - * The option to includes transient parent groups when determining user group membership - * @visibility frontend - */ - includeTransitiveGroupOwnership?: boolean; /** * Allows you to customize RHDH Metadata card information @@ -306,41 +75,4 @@ export interface Config { */ overrideBuildInfo?: boolean; }; - - /** - * Internationalization (i18n) settings for the app - * Allows configuring supported languages - * @deepVisibility frontend - */ - i18n?: { - /** - * Allows listing the languages the app will support - * @visibility frontend - */ - locales: string[]; - /** - * Allows setting a default language for the app - * Will be set to `en` if not specified - * @default en - * @visibility frontend - */ - defaultLocale?: string; - /** - * Allows listing of paths to JSON files that contain translation overrides. - * These overrides let you replace or extend the default translations provided by plugins. - * @visibility frontend - */ - overrides?: string[]; - }; - /** - * Configuration options for your user settings. - * @deepVisibility frontend - */ - userSettings?: { - /** - * The persistence mode for user settings. - * @visibility frontend - */ - persistence: 'browser' | 'database'; - }; } diff --git a/packages/app/package.json b/packages/app/package.json index 69eb6b459d..6a310cf57e 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,100 +1,115 @@ { "name": "app", - "version": "1.0.1", + "version": "0.0.23", "private": true, "bundled": true, "backstage": { "role": "frontend" }, - "proxy": { - "/healthcheck": "http://localhost:7007" + "proxy": [ + { + "context": ["/healthcheck"], + "target": "http://localhost:7007" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/redhat-developer/rhdh", + "directory": "packages/app" }, + "license": "Apache-2.0", + "files": [ + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts", "scripts": { - "start": "janus-cli package start", - "build": "janus-cli package build && node ../../scripts/generate-index-template.mjs", + "build": "EXPERIMENTAL_MODULE_FEDERATION=true backstage-cli package build", "clean": "backstage-cli package clean", - "test": "backstage-cli package test --passWithNoTests --coverage", + "lint": "backstage-cli package lint", "lint:check": "backstage-cli package lint", "lint:fix": "backstage-cli package lint --fix", - "tsc": "tsc", - "prettier:check": "prettier --ignore-unknown --check .", - "prettier:fix": "prettier --ignore-unknown --write ." + "start": "backstage-cli package start", + "test": "backstage-cli package test --passWithNoTests --coverage", + "tsc": "tsc" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] }, "dependencies": { - "@backstage-community/plugin-rbac-common": "1.26.1", "@backstage/app-defaults": "1.7.9", "@backstage/catalog-model": "1.9.0", + "@backstage/cli": "0.36.3", + "@backstage/cli-defaults": "0.1.3", "@backstage/config": "1.3.8", "@backstage/core-app-api": "1.20.2", + "@backstage/core-compat-api": "0.5.12", "@backstage/core-components": "0.18.11", "@backstage/core-plugin-api": "1.12.7", + "@backstage/frontend-app-api": "0.16.4", + "@backstage/frontend-defaults": "0.5.3", + "@backstage/frontend-dynamic-feature-loader": "0.1.13", + "@backstage/frontend-plugin-api": "0.17.2", "@backstage/integration-react": "1.2.19", - "@backstage/plugin-api-docs": "0.14.2", + "@backstage/plugin-app": "0.5.0", + "@backstage/plugin-app-react": "0.2.4", + "@backstage/plugin-app-visualizer": "0.2.5", + "@backstage/plugin-auth-react": "0.1.28", "@backstage/plugin-catalog": "2.0.6", "@backstage/plugin-catalog-common": "1.1.10", "@backstage/plugin-catalog-graph": "0.6.5", "@backstage/plugin-catalog-import": "0.13.14", "@backstage/plugin-catalog-react": "3.1.0", - "@backstage/plugin-org": "0.7.5", + "@backstage/plugin-catalog-unprocessed-entities": "0.2.32", "@backstage/plugin-permission-react": "0.5.2", "@backstage/plugin-scaffolder": "1.38.0", "@backstage/plugin-scaffolder-react": "2.0.1", "@backstage/plugin-search": "1.7.5", + "@backstage/plugin-search-common": "1.2.24", "@backstage/plugin-search-react": "1.11.5", "@backstage/plugin-user-settings": "0.9.4", "@backstage/theme": "0.7.3", "@backstage/ui": "0.16.0", - "@emotion/react": "11.14.0", - "@emotion/styled": "11.14.1", + "@material-ui/core": "4.12.4", + "@material-ui/icons": "4.11.3", + "@material-ui/lab": "4.0.0-alpha.61", "@mui/icons-material": "5.18.0", "@mui/material": "5.18.0", - "@mui/styled-engine": "5.18.0", + "@octokit/rest": "19.0.13", + "@red-hat-developer-hub/backstage-plugin-app-auth": "0.1.0", + "@red-hat-developer-hub/backstage-plugin-app-integrations": "0.1.0", + "@red-hat-developer-hub/backstage-plugin-app-react": "0.1.0", "@red-hat-developer-hub/backstage-plugin-theme": "0.14.7", - "@red-hat-developer-hub/backstage-plugin-translations": "0.3.1", - "@red-hat-developer-hub/plugin-utils": "workspace:*", - "@scalprum/core": "0.10.0", - "@scalprum/react-core": "0.13.0", - "lodash": "4.18.1", + "history": "5.3.0", "react": "18.3.1", "react-dom": "18.3.1", + "react-router": "6.30.4", "react-router-dom": "6.30.4", "react-use": "17.6.1", - "tss-react": "4.9.21" + "tss-react": "4.9.21", + "zen-observable": "0.10.0" }, "devDependencies": { - "@backstage/cli": "0.36.3", - "@backstage/cli-defaults": "0.1.3", "@backstage/frontend-test-utils": "0.6.1", "@backstage/test-utils": "1.7.19", - "@janus-idp/cli": "patch:@janus-idp/cli@npm%3A3.7.0#~/.yarn/patches/@janus-idp-cli-npm-3.7.0-f225cddfdb.patch", - "@scalprum/react-test-utils": "0.3.0", "@testing-library/dom": "9.3.4", "@testing-library/jest-dom": "6.9.1", "@testing-library/react": "14.3.1", - "@testing-library/react-hooks": "8.0.1", "@testing-library/user-event": "14.6.1", - "@types/node": "24.13.2", + "@types/jquery": "3.5.34", "@types/react": "18.3.31", "@types/react-dom": "18.3.7", - "eslint-plugin-unused-imports": "4.4.1", - "prettier": "3.8.4", + "@types/zen-observable": "0.8.7", + "cross-env": "7.0.3", "typescript": "5.9.3" - }, - "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", - "config.d.ts" - ], - "configSchema": "config.d.ts" + } } diff --git a/packages/app/public/apple-touch-icon.png b/packages/app/public/apple-touch-icon.png index 719f50a963d4e4dbb79e1d5f20069646c005add6..c4aaa2605f33254234baca95ca3f59980bdbd929 100644 GIT binary patch literal 13575 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

a=JN*Z{I0J+-iNF=DG`UaL;s1ds zuf@8XCsBOj(V#w^n|LJ>+9Vlc1k)A;&q#*nxXTPsP9n(sM<`>E@~tk-&GlLAhcTI< zRGE<96#n@3q)(fzFP0F-a9MUpBt=UkoGX-nGyD(=hF&cU4^#aR)b3U4ikoNyaOy0e z>_8o(Ed|kC>RG@Fh`5^u4&9YUM@P*~%c;#cXTqa54wm?S)=_-tG67+7OpAulxeKfB za?6^477eMebk`!2?6Wd6U%s(=sl)Ne?EV=}!6f0en@a@n`u$czc5@7|+U&dlQ_gJ^ z@ee5t*57G{**B(yFy}*91?iwiZ!qw|^-1~_V*dX(HqNs%&gpZd2_E+#2S8oYtlZo{ zHK5?&0x;<&sbX9YU=XJ|i7kLz^NSOs%dK!j>tQ5kwz~Fl!XdPB7KSd<7(C|Llln-Z z-LEIh4ab7HlU|E}dw)qoP3Pqt@HMRqWR(1om$&(Qdwbit$*{T^=oprWIp3iA4td}p zPsXm$YjzAPXl0;i319BjrNsp)$W|s=G>&JXTk*5h-wNkz&9$|)7gNr=mc?Tu0%U}* z{}Cr&HBAi=Z79Bl0q)En25kC8U>#IGwyrf)(0~im{=`bZ*@}d6z{WB2NOoSP zWKj{&Gsfn%ztT>PzV#I{(_`=Rat^2vr=ETbH4;x02gHfSK)Q}L6G|}$v9#NPeexct zO`t$V$XNpZe?R&ZdmgDg1nS74<{=>b_c|R!?4DK0YT!Egj`jHKIh}1i)c%0fy8UfKLN(O$iAJ6Gz9#mG%em z&N5CqOEcizd5#Vae*d1Gy?O*nMN03yBqiZxOa!tUDHK(~CozDLVlJI+)&YiQsHy3$ z3y{-lwJW+p#O5Z#E|5G_PY*TfU4Nj)h8?gIua3^n8;2L(OsUI88QFKKyk%cM(@lKB5uDFcwSNx-iM99MpMQ~@(0 zY~MqCyobIyIQUm|=(Bz>UFZhP>Oa%?<)eTIz$AOMteobgr={@=ad2>m0UuyWXvz0g zP&h{#|bIum^Db>mq~VsKJd_ a%zCh@;~+<2IZ)XIl9yJNs+KSf`F{XNK|?eE literal 9845 zcmZvCbyU<}^zG2yCEeZK4bmZB1f)|&x*0%15a|->E)gl|?v^f*2I+1ofp>Yox8D2b ztu@2UFrT=0?m1`gv-geA)>OhmCqsuoAXqBO3OeAm_W6m54E{=vBJ+V4G#BMJ?%@0R z=O^5D-rOhfPf`y>1CRfl-+FjkxYQ{w;eAL>CnWI{lu!?kAFRzdrZD5OX+<|Iy@G_(e% z_`4=n=WW@RA?&Z{y@Qwk{MBMd+x3Z(C;km48=6R%~qh64Ua#l|U2W}*?}dpefrq7~<;Z77HL2*>SW|A|sJ3(L4^5%7 zc6}2Q+Osz|H=0?R-lOCvYbFzfim%?id)HxB?=a5~Cg(Ii;MGF;vH(xmX(?zD+FNea zU|uu-{HQX3IaWf%bxH^=t6qRd6fvh96v`qeCx?rV5AWTkk(iiRe=t}1w7x%E`qbMH zGg_LP`1!NjYqr_F@bGX{{p^}~2Qc>+R8;+ggQzc+l<<^~6+WbC6NWOXCCilP((&=( zqldy*1P~lt`97SUo~~?fZ}Y7z^rg7y{P$lVIRU{%obi!$SkH&`b&JcZtGtd5YN3pn z6fe{plmz9EMQSOQeUU`kCAzsRPScE~vym$+E7HH_=BU>TD5)@fw|*w6>9Wlh?e6Xh z8Xq?paO@r)DtLPft1~4koe-m{Z@P*8`Sa(8+m@E<#O znqlc|McYocpOjfE>@0u&I4d(#K}iXvxw+ZA&Tu)PxN}LCE`df$iV6)44HX?d{pzu* zsw%_Z-+%t-=;-RWecDl$nTbj21qq2h89sjZ9rLb>$i(by==Szo*+C4#Dg(@NJ&Za- z$x>Fgi*1dAI{UeLVL`zx_Z<@_G~e0puTiY5te*HXNaQQ$3fkMLHa0enYcaTy>JpOT zs|mZh38+9>L0zAvYi&b=^slL@^K7-N7CMn~y%ts}sdja)6oLnSu&UomO1dT{;@OXI z4a`}RRLhJSu-%$6VdKyDD=lT$DxNmkG~~9GWoBR~1W#QQ6chr-?Xxpi+uAZ0GrDUA$3{fF`Zl{SCFK0cv~nQDEP%Ca6Rq*1 zE?ZyQ^$JF)oNTMDgTvulEv@nMv9Ynq`TdPruO^SZDXsZl2@#R-pFdS)M?eZz8EC(G z6VcTr`$uwpeZ5Pys5)59zXn~5k#Q@@W;nT=E{PlySyx{lgWVtA2_&qxi3wJlsaC7) ze6|$Bi$Osqx;!Va} z*cce>Z`m?vcMA&(t&>#iviIJC#JRikwGcJxJP@J4e*bVar%KGdI^bto0kXP|2q`VUcGwtMx97qQ&X;Z z`fO4Y4sLAMLo9XlZGZK*Z{K3<>cswz*?Xgjfr`d3q%k)+NqcBZd@4#Dg+xh78EvCe z@+DnLb(L`!lm!Z%qQX2gna)KIFE9K#aXLD>`lY>;cFx_|QhjN~Na)P`JZtnAd_a#~ zU4BhXVigAigFNsFF9^Us1k%>W&gyUnU`~i9}6O^$WMM&-> zG?);*50eBHf{BHN#vTkGFksDR0fVJhspn}I7JdEtGl*4It!Qe%hL3c^`bTBuxP1&E zt_#vRgEq4yV~!lw4QK|$=GI`lX-@Fx##og;L9$qfk! z;ZJj#I=JforTrnOi>}*0F$2GRJU)a^vRV3h_$oJAx+uF+S-_g0- zZ|Fd2`!`kJG!f_!9TS6h1dULrPWU-E*!bi9?K$T(2B=IfdsB?g4RQR0iWcM9k~g!J z0inIU3Yy~{-rgHUDW=^mC+!D`l)<#Lw6NQ)6xYk$$v0L_0M%O0`pIE?MJX16Z0a*7BR_Ph!{4 z|M4S!KyPv+^wAyVvydX)t9WI(-=ildqsoMedEdUFlEr*#5F^0H*ETR16m6Cu@j6+F z;WT=yE^Exh&5eVEgd|ZL{H zr-P11cITsX_J%(>mTp;ODlgkuS#`X=d>s&D$a3-Tu(Gvb20r8F|tnRbhhF*0=IVi z0*#)Yp0v_^-feMux(Akp!`*GX2aa|L{>p;Ru2*KxA@05%J$&VVIjIyq2W}^kT^<)5 zjeC%dH8s^xd~dt0^W+l}MBrg<&EPtpdSsu8$XfWtn9CNcdK77jB_k^ft6Wd*J(s8` z>6+6MPW@mYmZQj1kudgRo zilb^f3JW-H+R-*LQu7F#otbI0-r&odJBSE@&v~%rOMB5K^2oG?cm%k|;^MGDGDt5s ze{ruv^VW*#J=hzGyZP~emmE9kG=ik7S;no;MFCf#Y^&PnX5gSB;XyQK7r! z-o;PKOnwx4gxcGDSNXQD8zPMxV|Z@L%{AD_>j5)>O2 zHa0pZ9cj<~FG`Lce_TXd);i%Dn2a^W#Km22PAyCq5aI)c)6_rZ=hJCEkdTr6TnH>2 zA6!K&%;h$1Rn9cAwYJX9&ky!2oP-JhaO!OKJa{pjk)Ez?YC5K71h?QcIy5Bv`gPLq zIG{~*%*djEQywyS$Sd7;jpblAwyP+L#0U(M+DW#*SJ zm;ZeYURg0+YW5r+7|p9%JbLlsMdwPJAL(!kr_m?dY#(HhZ*&Q$fHAWrp2@q(Pa&TB~tF)JJ$2_b690dULG8d)D4ZjlY~|{SoHOtu!NkP zxZ2v<#l1icCW8HggI78_!QQL>DI&L5Uv_t)Uy_n6uJ&hJ=S&O@BOMp(Z$0H+kdj8+ zA6rSFlQ#wsKa;+s(u>g4A0YJ1$En2Z!E7Eh&}D0G`RIsbERJzvheej&Ra^ z;%HFvbhHxP$jnR%LPA0sVPO)${~DN0u)`2WM@Mf!aJ%f!uzpEN0YpH)NGGqX43m{L z8ZQKHf}k%mGgCArke7`OJ%!68{q@N6PmGL=OoJa0pO_c|N|^aZe~hxGW_z1ik2POj zaWM)Bm$3}My`RIw4*`U65u)O(o2sC$FyNzsLLE*GA$dhYLShAgdByIlJ_{FDe}zd) z_?CBM8d`)x@Zg}bLHRGyE1&Tp6F@7Y-vBvhFreD5_Dtg9VW%jZ-7-x{FA-k%<3ZGA0A@ z7w}^jj*X5gT3KyGSK~=Fls=~_RwhS+bBIFSV;!Uz4`5?KC z?~o|>mr?)&;*ErhwH5}!Mli?86c@LHa6@iLASduJq6!Q@TGTWQjH%62M7*FUxHZ0@ zlP-7O)d-5tK$O$18S#%e1D+qH+~F9Z(`$1OV!t!VsN$6~+FfUOxMS4d?Lj+8Ai|nY z=Bwi+oLtOKHtKfwikdY?DAy8(uQm3LOC@Qo=(#9#{3SKS_G`Kegf|zd8Bc~dXudj? z1L2f62q9Mk56%lwcD&opg}|LVNEq%P5hTO;8*9C|AgetXpJW+Xwc%pj+)vM+Y6(kT|CN_qF1~t9&8uCL?<`K112x219UV|ee74&4; zv=ab%>f8|DArHxP#bs^C(7R=1VgwizN*5>_b7m8Dd8q%7p+&p4X@mf}bk67Y=C*L7F z{I+auft!Mj9oIc6{`+e1E1Tib(Y*i}&mQkjYP=+5n5#HIGBmmGEXA7qL)>MAGCh(& zuQMxmkx!jJ@`>*HGaRoSNL?|)aqgz3z5osB{FX$JQG-+8)^Lib;#AIARUZ#IoGLvnVt3o=;d*vsGS4hAE%lIMEq51ewm9lAMGb?l9G*p@B9k)w^jX3JDl)hLGd)$5$q!{}P6`+{UU>RPZjP zb}nnZOK6OD(uWq!{P*pj0agIt#KZ*9})A}8{5Z)BjQTq5LT5>3^;~kJ^hz zi+ftAzipI*oS8d+PPh{I^iVeLw9tFl3xJyV(pU1JM%duz4L0ruwu56TCsA9+|C~yR zjRLTVbU2n$ygxOID7Ql|4!kqaGc%L653kIiIhjQLSjYQ0@sRlU(+jw#s}gBIyAU?N zSUfjJfsYcOKYu=MK926!GECn396W-Zo2cC%i0V2=TbD-;A$fCKp!Fat^HMW1CMHy~ zxP|iCM@%RK3jo{h(UGEq14ngrb+~8+0LWTH&SFH#6A0|f`du;P-%;V+i;*anK%`TF zu$_n5;uZ5TMa})ay)JU+)(_N=r(w}sfC!KaJ8kODodjHqk5QVTy_L275IfZ6d@gvb z07t`V;656?TPbxV2=~qn(s{Nqu&iqB!z?H$Sk}~7eBs^g$qLzyj%c^w9;6>vB-uYx z%DGDqBDw@Z-4DNOrv@!`AoExb#8Ptgy|;%@iX=qX^7vt5InyAVa^#g!S-0f;PY@c& zB6tWbmX&d2ihGY0?Wn4$B_t(o!4l9#w%$4jF2VjJn{WB?l15|pSubeX#x_@6*{@P1~a%=>R+{ej~VAKfj`?{oTf z@W1nKZ_8Z1TyJ$O00eqyULNh`-@kwU-Ch8hWNnM`30VxCg+O0MiRb)pQ#TDH2V$Va z@h?K7)zB@^f^y!V1 z8OSeGSYi(~W-t|;$|1zRJ%0T6gl6#9{kLw^@RxKyabzkT&_@(-1wKf_HTLQ}vh4Q7 zicqbR;w+5NM36#O5Ga4?2a(E7ARgvQV$Que9)kFP-eiGZr?(73B!pj+42`%{Q_0OR z2qDVW@F0qn!5JkVXhWn?PKlsqb?lZ=@JCkd@M|^@<1roP57-0j;pu6j!mO8qF}WLN znedcT$-Qon4#ZRbiQ!42+&GP^V9~YPhuoB!3A!- z7@b^zgHYls9H9s2n)G6?1%_xcTvF3;C$@x4KegelHgA8T311*0ZhaOmoQsLPLTM-b z#D4`7=P|$fasc<9x|3{Tc)sDq3S)RWIfmW@kb9mf$IXpM|G>a=<)!E2o7(b4mW?_! zouEI!$PJxE+2`bN zF*If_Au4 z#`tefD^SkKiA#a*Zq7YYl#wyiz!@;?Z2x2rUv3^e1<6EFe%3-3bieE4KJLINS_1`kGHg)?93|uDU*_=B^{Sp`(brz6AyLo zp#qOI6y9(Gh>SOziCVdnE*hCC4UD7NhY$nZ5_DAOk*VZl zWT%}7IJq4iIoHi_Vvs+e(!4S;@%Nh&6A(ZI^5pmLY^^tcqQav#E>3?nh$xQM=cl4B zNSr<9q@DnDJXvl@nH}ND7=;~_H~etl(JU+~qUGeoYVkViUUcrjs!5Y9SObv3BeLtH zW5M?<8(q!n=S}aOo)2?dJ=|S+D~{oyqszU0%M|cb$g|#7AzNGIWa` zf~$;ZZHx^Vcz^%PeBpxTX0rS16XyPC?b2Bh|2qLbzEqtK1Pu)h&`g9bJMEN@{$K5g z-^90~q5}K&_ICI9c)OfaGrv>nRqY4tOn!fU-P|7H6nYw^T~4F=ACb2oP^iqYf~Hnh zVl^^^)_*3kM{kwtmCFIyj@uDww{s2fceghqkSw*9*48C+g7Ll zqf3z8}ez0xvVWyVeTKzV4bwH0#^x_atcBG0m=r$Nff z$QXvT{rE9aG@BFDa5bb*NXm~Wd<3sMn^|8Y|mt5AAP|nG#CVJDtc%)y>r;h0$HFi zS-pAvnh3Os$EtR3%*^7hSNw25>uFWnFjrv$oQk7a5%-JH-c-%bmjky zYkGdb49d^Xhqs@ru;Fli_fGcBo3w!=hM05w2t=^j(X3%a=t!WGk5J(p&yhZHZP^xQ za9lX@3I{Dv0qsy&+%FRjVvKaPDFMb~ zBk%B`@wM-2<_~9nl^b!oHNEer1jh!9605!pW z%md{T3t%dPLqm8=o?J3hU*{dD@HI3w`NrgKup_q6kVD`=Z_49!_xfLqc_muk)CY-a`cT#j&^JJ3KinU;jI(Fl@9=X3I~7k&O_|2T#XY6$ zg#sTm=l!`#XK4Rt)go|5;J0)g9TtyO(tzriolRxj=mN*c$Ox3>oxY59HsJP&yuaZA zKOxX@z4Y(@pL8-;nRwh3sm#z#A1Beh+k9|*91yCiqB5TtAAhR9a#}QDb-oFa zGQ1@7L#Okqv0R?^vqBJU12_*7e(9|5ueUY4Vt2su&BXP{#bBIR6i%Tz%BeCe$Bs~N zlbu;wk^tgF*YAyELRq$E!yhA6o(t>)L()`~%K_I3y<0=cegIQBhGbsJ2G4N*l7Sj){&weEV$nDJc!e1O`4) zD|1gn1;QgE-AeP|;cI|n^rlA_7*S94z@y`p#pb=~2i4+9rE-q{rEw7pI$#h4dbmh1 zCE&i&(}zIqK(XJgG;O<+Rv9Ni0tzriPfw4Zic@QDE)B>r3=A&#R@x-hgsdzL%wmTs z0}4P6TPQdNz8Un;3knOTT>bsqF2{z0fg!J_m;9qox6`WElJ~gn+Vo>+DBj85(b2>8 zv-4avEAJ}G-)PbEsT63G^i0E46ch@co`SsFRY4GP(EX&Pr2&(^*YEEBzN6y%cSC+7 zlT7&ESU5O=-VP4YsU=E9I%wNrK}I5@m_od9F)^e{N=hd?I@HXv-d-Zgc`gxQf9oH;1Lh1Qlk9&G6!Z?P=Jt?l@)+C&ba1RsKfeaVf3^WFx}eLvv9hggnf`vp zN>*G&OY7M`Y z$N~)%h)uvGACCA6gw2=8C@9|J^_fTLt>_Cw>4NqFM3j{N(W7RgpO&2lMl*z6H~O)b z8eI{9ISLqvP{!znM?ZW82y0s6DB1*Nnv`KYGO|dp3kDGdr6r4X)S7R;!mQ)bS6;pc z=eH19Q;0qw6kzJZUsSik*pk@PM2y)X69ZqLGZzcFXDl{07S6e~$3EB#pM)d@mz4B& z0JN~2#z*!aZo&QEKi8X%NRTY4!0m{mQ7wDmXOw-hfgUO*32(C)fslw8*t=ksjFuKglgM5sTaF_vi9yC?z%AW R0b9l(DvFv46>{dm{|f+>0=WPH diff --git a/packages/app-next/public/favicon-96x96.png b/packages/app/public/favicon-96x96.png similarity index 100% rename from packages/app-next/public/favicon-96x96.png rename to packages/app/public/favicon-96x96.png diff --git a/packages/app/public/favicon.ico b/packages/app/public/favicon.ico index 6cfdc82c06de15c0380bb01b21c98d657e5a4631..c023bea83513dcdcfb1148b09e1a7e19de323d25 100644 GIT binary patch literal 15086 zcmeI3TWA*B702gGyv2Hn81IEdL{2OyK{0C8YP`g|prWNO#TV&$(FY$@X!M*`1*H!? zRTS+@3sn=P7~3AuhgfT&h#my-(sMM?zSSb76co+L?)vSSmDw|y@0)MFFNq0sU}g5~ z*=w!;T6?X1n=hW1^D4cD22bvF-le&o*XVg(U0v{8?RhVxu3GNA|8#`s{ahNZY3?!igT|$3pZf^bz3|X-+U%vbk z;cef#b*sg@dGltoV#Nxxc=2MhXwjkpC3K(*o%jh18#ZjX0j6T?Si5%ZPs9iB;9at0 ziJ3Zes;Q``Fv2^igidrfHa1#21k+ov6|zCz9|O0&ECCx6ZyuC z8)vRxzi!*{hcb0(IXo6ckdCbHz)TK6Y50kEF8uE5dGX?fdGO#tq>lGjuU<9p-@iAzcki}@ z?bNxvVHtm9A3L((A31WQjrY#ZPBU}nOp9a3jvY3B&YwTu2iN@h^Ua$#Z>-Jyp-f$f zS=UmG0T+e-DU{8IEcnq&t`hIW%=72ZZ5eEBZEcBT5q2IpaKKXX7*r{kF9^L6!UjJs z``W0sM(#yc^C-@(px>C&a%yeLk;i~ZMvJ$)>W%Pj?eTn2Lh*op7y)29m# z*GFKg6{-^gufC&R{azY&Wc3Gr!OcTK@dJKx>h$T; zHpi}AyH;ZUlH0@%xgN#^V*xS;GJjot$>p^16YM&^(l*3jN;ymX7cbwPzZi>J0-}uH z!B3m=u*2V9`17wH{t;jXH)Ucb^gC+~-3ygsPk-P~U%%A}X2{P~#R+!C0M>rRoC7GI zdoKk)dtLU6te?-FJ6H1h8NY*_i#0g1kcsT1{j`f)%USWe`xo|&>}6Dck`EcbnctwK zu^b-VKOhU4?2RbrJJ(R`+_T`<{Uc`%@%&6_uS#|-+R-18IS z9U0oE zlE$8RTVxUMX)&XG?)mL-KR;i(bm_d|{QW}u#2ZfEk7{aaBHxd|uix+ZMmZe6-(iD% zSN)W;=;84F6s%y5Uqki#H8JCSzy3Uq+kNIcHs4q23%>IWq2CX@%jb6&KV|a4{rv{> z!^i&pMk&8TVQiJL>6py(_B)?`Z_@8J193-3Ft76ana{xx_pkfh!^2NK?;ZDA&-*v` ziJsTReYEFwaIch|EcaY^=kfnn=z$iBj(<9M7Qc0I7eA_B)z9j8e%w?iv|6ax-)TuK zR!QIfK>GQY5?^1-ckpg`Hsug9Bu>8(`U2VL5J>SWC%-+JE_#0}?f*4z-n?!Zn|dZr zoM_ihte;9HWFV`)zP=mT@_qa_=n??pRsZ5yu>C=>eLH^q_};yQyiHvT7TbnB1hOS> zzC~Anf!}#lfeGD%F-M~VUHBT^^a*|eTd}s3+YVjmL^p9k-{d><$yZ+v24tcW-9ldz zA8~n=m6c}p?Ad1f_U$(QneP+}_v-3u`&$csZ$Vk##pUFCMkl((58cFQydApl-o0yX z-@a`gK742{U%qU#9oX2j-@A9u5@qV*G73E-3*FKOefB5Vg5BsIHENXo4*2BB6SHvP zLTe{=ckbLVJ9q9hyLRoeW$HqGIVnfa#=>`gPLa>(PSQ_X8Go$Z)22-$XUwx_&&+}a z3#@)@J$Ue-dGzR!CCZ`w_~A!FKP!kKO+Wq39{cg*$5!{#r%&yf2YWR9!(Ozxd4nZ? zUy9A>-X`={Auv#vJcgv{M+bd`&#{~H7O?m+P$6>BRTB_p_N_k@x+k<-ejV>>Y)rxfFEWr7Z$B9N86Qg3kIy)}%I1IirhojOJ^qMEV$&ZV`o?aw zzta4#{RIxFaQ9sV{M*sd zQE>hxzuoy4K739eFmEz;GKV9JcdifA|7q(FFeI%%$jzkn2mOuD;XyZR7}hebZpySn zH*28DlP4Ekf6*85^%vs<;~GR+?c+{cc(5DY;o2bV16_Zy1{|pMr(%lRPQ9)_#Rsc| zKHB=5Jpg+F_9Y)<{~^BdB2fBOr zp9(_vW5lGyZ-4K-rx*tE*rD literal 9662 zcmeI2TTfL-6vu}E5_s{Fh|r4k!3qW?Tnd8mmPjCozIx-OFYq;L!X>sTnBpaA(l&ij zDA)I15;TFpC(t&rwcetTG<^hArt3H7O!u69_C9BC1ba+6WSyBkd-kmVS~IiOni-VJ zlfSw;CBGl3v5%E1RZ3M0NtOCki2AUkwN_zt;J|_Ay1KgYW5G0vhePKGMUdC%0 zdE49D^`1R@-YMQp`T;D zs_GNQ2fgPac2@?*0jwfJos~h>%&)AKT$(c$vp)wTSOxQa@QbZ!VN6)7{ zHc8gQ1oD)XmAyCkv58<7?4#l*md~6y6WK-<78dHgd-q0?b?45V8rtk}#J1ue@NsQz zZL23wp3KUFbv-gNqE}W{WZG%{{P}Zz{`~pC@30jU6BDK#KjY)$n{F4dlQt&6=VjB; z_3PISo_qK1>1)@n>8DSh>NjuR=o2SSq+4HvmyKtp_Yxl% zJH$L9Cdkxw+qP|bb#+zWy?a;Z<>i^Sm6esIZftBU?c4eJc_SZfp=ZyYY3efZi2DxA zV2^^oqN2j+aA;^K1;djkPxRvAVoDu2Mn^~Wt5>h|rAwFe<;#~%`Ocj?Y3)4BJ2Jcr zj$7j3M~5$7ywHyyKi0?s-wPKm7@2O~yqU(ky1H5~FE5+-{7@DjnbuCJ0;&5}=%vv1 zfcL@J;U5P-?Jr)uXy!Zf2svz;nwrws15cL;o~5Owy0^F25M}@S1orQw{x6|#0^WyX z-w1xb!A@aI78Vxtty{PB6&wic6{=MYs@@M`&zW#&yL+@Z;TU!gT z^M!)z10j5c8-Ih<>YY43cCf^qKSB6yU8;`)^H)ND2r)Mx%3lic?(MN}HhwFIQZ0h- zp%9pV78*#>FUh*4WWI5|TV9)~|3sf33O2Am66z3g=R4(2p+AL?fpPG^kM|rGKQTdn zf4`oao71;%-zN6+VPMXn_jX^siwhZO!(8&}v90t;-}vb$dyu&Jt*ybAzJC2$FD)(U z-Me=uaj*s-3H_9U%f;xGnM)|dNSEPl@WYFgjz3C_hK%4xW@Y>v zeOdhI2>j^G)wjPK2S4A^FUg%ho?o@|=f{jZ6Bw{d%pbl{X3xs}AzrfchuGVX*Z&+B zKkIjJaL~k&#A503%!u$ruWcE4W0z>dTKDDz@e+L!qY`_=%Y7eIj*CC2kJLJAKhbTF z4A>>sI5zg(+s{<2p6*iXUH`>gvi=LZgx%x=xnSG1(Y3#Sc z_=MH-4GFtOEKZ;Fef;?GNPWe{&wh*jI(lQ@XcPQ{bck=pKR}+J_Qrs3tWBmb`lN4o zgq>@+x`>0{?jPZ?Z{I$nANFIUp#3AZ)!jc5Yoy0Z%IKXw&<)r)6X0Bfvl)20c>U!# z`0f6Ecz8Ia&usg5;*9R@ZsVJX&-@tJ`?D8y>)}Dlc19Kle^F79IV+i+o&8rYj01aN z=B&$~a@6w^;xx{~?3n;M=ZuCr&TRbsy3ZCrXVFp4uWsD9Vf2Cw=m_11onPUnNz8BJ zF%q%2%bzl5X!OAt&?neQob&nH`k%oJb~%GvV_)un?|&vH=L`;=lQ>VY^VZ)M|L?9% z>{M7xz&fHY5`DuXO8gJz zR_@p>esZ}RzRH;7>isF$!3M@dhYo$=Vz=dnhK3p5BV#VjCFTs6z{VZB#T%5%IDF3d zupU^`*rosC{|Dd+Z`LYUz{DMU5N3ZJ_mZ-vA0RK^a=(Yaz~|w&x02uiFL=Tm49(5W zzwh6_ztxX3+cUOR{KKTg + @@ -7,51 +7,15 @@ - <% if (config.getOptional('app.branding.iconLogo')) { %> - - <% } else { %> - - - - - - - - - <% } %> + /> + - <%= config.getOptionalString('app.title') ?? 'Backstage' %> + + + + + + <%= config.getString('app.title') %> diff --git a/packages/app/public/robots.txt b/packages/app/public/robots.txt index 01b0f9a107..7d329b1db3 100644 --- a/packages/app/public/robots.txt +++ b/packages/app/public/robots.txt @@ -1,2 +1 @@ -# https://www.robotstxt.org/robotstxt.html User-agent: * diff --git a/packages/app-next/public/site.webmanifest b/packages/app/public/site.webmanifest similarity index 100% rename from packages/app-next/public/site.webmanifest rename to packages/app/public/site.webmanifest diff --git a/packages/app-next/public/web-app-manifest-192x192.png b/packages/app/public/web-app-manifest-192x192.png similarity index 100% rename from packages/app-next/public/web-app-manifest-192x192.png rename to packages/app/public/web-app-manifest-192x192.png diff --git a/packages/app-next/public/web-app-manifest-512x512.png b/packages/app/public/web-app-manifest-512x512.png similarity index 100% rename from packages/app-next/public/web-app-manifest-512x512.png rename to packages/app/public/web-app-manifest-512x512.png diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx index 99089718a6..d00e40d327 100644 --- a/packages/app/src/App.test.tsx +++ b/packages/app/src/App.test.tsx @@ -1,43 +1,33 @@ -import { lazy, Suspense } from 'react'; +import { renderWithEffects } from '@backstage/test-utils'; -import { removeScalprum } from '@scalprum/core'; -import { mockPluginData } from '@scalprum/react-test-utils'; -import { render, waitFor } from '@testing-library/react'; - -import TestRoot from './utils/test/TestRoot'; - -const AppBase = lazy(() => import('./components/AppBase')); +jest.setTimeout(30_000); describe('App', () => { - beforeEach(() => { - removeScalprum(); - }); it('should render', async () => { - const { TestScalprumProvider } = mockPluginData({}, {}); process.env = { NODE_ENV: 'test', APP_CONFIG: [ { data: { - app: { title: 'Test' }, + app: { + title: 'Test', + support: { url: 'http://localhost:7007/support' }, + }, backend: { baseUrl: 'http://localhost:7007' }, - auth: { environment: 'development' }, + lighthouse: { + baseUrl: 'http://localhost:3003', + }, + techdocs: { + storageUrl: 'http://localhost:7007/api/techdocs/static/docs', + }, }, context: 'test', }, ] as any, }; - const rendered = render( - - - - - - - , - ); - - await waitFor(() => expect(rendered.baseElement).toBeInTheDocument()); - }, 100000); + const { default: app } = await import('./App'); + const rendered = await renderWithEffects(app); + expect(rendered.baseElement).toBeInTheDocument(); + }); }); diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 6be8d0c837..3342bc06b1 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -1,31 +1,41 @@ -import GlobalStyles from '@mui/material/GlobalStyles'; +import { createApp } from '@backstage/frontend-defaults'; +import { dynamicFrontendFeaturesLoader } from '@backstage/frontend-dynamic-feature-loader'; +import appVisualizerPlugin from '@backstage/plugin-app-visualizer'; +import catalogPlugin from '@backstage/plugin-catalog/alpha'; +import scaffolderPlugin from '@backstage/plugin-scaffolder/alpha'; +import searchPlugin from '@backstage/plugin-search/alpha'; +import userSettingsPlugin from '@backstage/plugin-user-settings/alpha'; -import { apis } from './apis'; -import ScalprumRoot from './components/DynamicRoot/ScalprumRoot'; -import { DefaultMainMenuItems } from './consts'; +import { appAuthModule } from '@red-hat-developer-hub/backstage-plugin-app-auth/alpha'; +import { appIntegrationsModule } from '@red-hat-developer-hub/backstage-plugin-app-integrations/alpha'; +import { appDrawerModule } from '@red-hat-developer-hub/backstage-plugin-app-react/alpha'; +import { rhdhThemeModule } from '@red-hat-developer-hub/backstage-plugin-theme/alpha'; -// The base UI configuration, these values can be overridden by values -// specified in external configuration files -const baseFrontendConfig = { - context: 'frontend', - data: { - dynamicPlugins: { - frontend: { - 'default.main-menu-items': DefaultMainMenuItems, - }, - }, - }, -}; +import { rhdhApisModule } from './apis/apisModule'; +import { navModule } from './modules/nav'; +import { userSettingsGeneralModule } from './modules/user-settings'; +import { rhdhTranslationsModule } from './translations/translationsModule'; -const AppRoot = () => ( - <> - - import('./components/AppBase')} - baseFrontendConfig={baseFrontendConfig} - /> - -); +const app = createApp({ + features: [ + // Upstream Backstage plugins + appVisualizerPlugin, + catalogPlugin, + scaffolderPlugin, + searchPlugin, + userSettingsPlugin, + dynamicFrontendFeaturesLoader(), + // RHDH modules (from rhdh-plugins workspace) + appAuthModule, // config-driven sign-in page + OIDC/Keycloak/PingFederate/Auth0/SAML auth APIs + appIntegrationsModule, // SCM integrations + SCM auth APIs + appDrawerModule, // drawer infrastructure (lightspeed, quickstarts, etc.) + // RHDH modules (local to app) + navModule, // RHDH-branded sidebar (logo, menu ordering, drawer toggle) + userSettingsGeneralModule, // build-metadata InfoCard on Settings / General + rhdhApisModule, // storage, learning-path, catalog-graph APIs + rhdhTranslationsModule, // RHDH + plugin translation overrides (de, es, fr, it, ja) + rhdhThemeModule, // RHDH light/dark themes + ], +}); -export default AppRoot; +export default app.createRoot(); diff --git a/packages/app-next/src/apis/LearningPathApiClient.ts b/packages/app/src/apis/LearningPathApiClient.ts similarity index 100% rename from packages/app-next/src/apis/LearningPathApiClient.ts rename to packages/app/src/apis/LearningPathApiClient.ts diff --git a/packages/app-next/src/apis/apisModule.ts b/packages/app/src/apis/apisModule.ts similarity index 100% rename from packages/app-next/src/apis/apisModule.ts rename to packages/app/src/apis/apisModule.ts diff --git a/packages/app/src/hooks/useThemedConfig.test.tsx b/packages/app/src/hooks/useThemedConfig.test.tsx index 4cddc7e69f..62eadc08f0 100644 --- a/packages/app/src/hooks/useThemedConfig.test.tsx +++ b/packages/app/src/hooks/useThemedConfig.test.tsx @@ -14,8 +14,6 @@ import { renderHook } from '@testing-library/react'; import { useAppBarBackgroundScheme, useAppBarThemedConfig, - useSidebarSelectedBackgroundColor, - useSystemThemedConfig, } from './useThemedConfig'; const themeWith = (rhdhGeneral?: object) => @@ -27,22 +25,6 @@ const themeWith = (rhdhGeneral?: object) => : {}, ); -const themeWrapper = - (rhdhGeneral?: object) => - ({ children }: PropsWithChildren) => ( - {children} - ); - -const configWrapper = - (brandingData: object) => - ({ children }: PropsWithChildren) => ( - - {children} - - ); - const makeWrapper = (rhdhGeneral: object | undefined, brandingData: object) => { const theme = themeWith(rhdhGeneral); const configApi = mockApis.config({ data: brandingData }); @@ -109,74 +91,3 @@ describe('useAppBarThemedConfig', () => { expect(result.current).toEqual('logo-dark.svg'); }); }); - -describe('useSidebarSelectedBackgroundColor', () => { - it('returns the sidebar selected background color from the theme', () => { - const { result } = renderHook(() => useSidebarSelectedBackgroundColor(), { - wrapper: themeWrapper({ sidebarItemSelectedBackgroundColor: '#abcdef' }), - }); - - expect(result.current).toEqual('#abcdef'); - }); - - it("defaults to '' when the theme does not set the color", () => { - const { result } = renderHook(() => useSidebarSelectedBackgroundColor(), { - wrapper: themeWrapper(), - }); - - expect(result.current).toEqual(''); - }); -}); - -describe('useSystemThemedConfig', () => { - const originalMatchMedia = window.matchMedia; - - const mockPrefersDark = (prefersDark: boolean) => { - window.matchMedia = jest.fn().mockImplementation((query: string) => ({ - matches: prefersDark, - media: query, - onchange: null, - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - addListener: jest.fn(), - removeListener: jest.fn(), - dispatchEvent: jest.fn(), - })); - }; - - afterEach(() => { - window.matchMedia = originalMatchMedia; - }); - - it('selects the branding variant matching the system color scheme', () => { - mockPrefersDark(true); - - const { result } = renderHook( - () => useSystemThemedConfig('app.branding.fullLogo'), - { - wrapper: configWrapper({ - app: { - branding: { - fullLogo: { light: 'logo-light.svg', dark: 'logo-dark.svg' }, - }, - }, - }), - }, - ); - - expect(result.current).toEqual('logo-dark.svg'); - }); - - it('returns a string branding asset unchanged regardless of scheme', () => { - mockPrefersDark(false); - - const { result } = renderHook( - () => useSystemThemedConfig('app.branding.fullLogo'), - { - wrapper: configWrapper({ app: { branding: { fullLogo: 'logo.svg' } } }), - }, - ); - - expect(result.current).toEqual('logo.svg'); - }); -}); diff --git a/packages/app/src/hooks/useThemedConfig.ts b/packages/app/src/hooks/useThemedConfig.ts index daedf86983..0edfec55a3 100644 --- a/packages/app/src/hooks/useThemedConfig.ts +++ b/packages/app/src/hooks/useThemedConfig.ts @@ -1,5 +1,3 @@ -import { useEffect, useState } from 'react'; - import { configApiRef, useApi } from '@backstage/core-plugin-api'; import { useTheme } from '@mui/material/styles'; @@ -39,46 +37,3 @@ export const useAppBarThemedConfig = ( ? fullLogo : fullLogo?.[appBarBackgroundScheme]; }; - -/** - * Gets a config value based on the user's system theme. - */ -export const useSystemThemedConfig = ( - key: 'app.branding.fullLogo' | 'app.branding.iconLogo', -) => { - const configApi = useApi(configApiRef); - - /** The fullLogo config specified by Red Hat Developer Hub */ - const fullLogo = configApi.getOptional(key); - - const [colorScheme, setColorScheme] = useState<'light' | 'dark'>('light'); - - useEffect(() => { - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - const handleChange = (event: MediaQueryListEvent) => { - setColorScheme(event.matches ? 'dark' : 'light'); - }; - - mediaQuery.addEventListener('change', handleChange); - setColorScheme(mediaQuery.matches ? 'dark' : 'light'); - - return () => { - mediaQuery.removeEventListener('change', handleChange); - }; - }, []); - - return typeof fullLogo === 'string' ? fullLogo : fullLogo?.[colorScheme]; -}; - -/** - * Gets the sidebar selected background color from the theme palette. - * Uses theme.palette.rhdh.general.sidebarItemSelectedBackgroundColor. - */ -export const useSidebarSelectedBackgroundColor = () => { - const theme = useTheme(); - - return ( - (theme as ThemeConfig)?.palette?.rhdh?.general - ?.sidebarItemSelectedBackgroundColor ?? '' - ); -}; diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index b93c7a0268..ece7196ce0 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -1 +1,6 @@ -import('./bootstrap'); +import '@backstage/cli/asset-types'; +import ReactDOM from 'react-dom/client'; +import app from './App'; +import '@backstage/ui/css/styles.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render(app); diff --git a/packages/app-next/src/modules/nav/LogoFull.tsx b/packages/app/src/modules/nav/LogoFull.tsx similarity index 99% rename from packages/app-next/src/modules/nav/LogoFull.tsx rename to packages/app/src/modules/nav/LogoFull.tsx index 4d479bd2e3..e4130ef482 100644 --- a/packages/app-next/src/modules/nav/LogoFull.tsx +++ b/packages/app/src/modules/nav/LogoFull.tsx @@ -7,7 +7,7 @@ export const LogoFull = (props: React.ComponentProps<'svg'>) => { ) => { { , ); - expect(getByTestId('app-next-full-logo')).toBeInTheDocument(); + expect(getByTestId('app-full-logo')).toBeInTheDocument(); }); it('when sidebar is closed renders the component with icon logo base64 provided by config', () => { @@ -117,6 +117,6 @@ describe('SidebarLogo', () => { , ); - expect(getByTestId('app-next-icon-logo')).toBeInTheDocument(); + expect(getByTestId('app-icon-logo')).toBeInTheDocument(); }); }); diff --git a/packages/app/src/components/Root/SidebarLogo.tsx b/packages/app/src/modules/nav/SidebarLogo.tsx similarity index 100% rename from packages/app/src/components/Root/SidebarLogo.tsx rename to packages/app/src/modules/nav/SidebarLogo.tsx diff --git a/packages/app-next/src/modules/nav/index.ts b/packages/app/src/modules/nav/index.ts similarity index 88% rename from packages/app-next/src/modules/nav/index.ts rename to packages/app/src/modules/nav/index.ts index 2b9d217586..add8a5dc6f 100644 --- a/packages/app-next/src/modules/nav/index.ts +++ b/packages/app/src/modules/nav/index.ts @@ -5,7 +5,7 @@ import { SidebarContent } from './Sidebar'; * RHDH-branded sidebar (logo, search, menu ordering, drawer toggle, settings). * Overrides the upstream `nav-content:app` extension. * - * This module is intentionally kept local to `app-next` rather than + * This module is intentionally kept local to `app` rather than * upstreamed to `rhdh-plugins/workspaces/app-defaults`. See * docs/adr/0001-app-next-sidebar-module-location.md for the rationale and * the future-upstream follow-up. diff --git a/packages/app/src/modules/user-settings/GeneralPage.tsx b/packages/app/src/modules/user-settings/GeneralPage.tsx new file mode 100644 index 0000000000..b4d424041f --- /dev/null +++ b/packages/app/src/modules/user-settings/GeneralPage.tsx @@ -0,0 +1,28 @@ +import { + UserSettingsAppearanceCard, + UserSettingsIdentityCard, + UserSettingsProfileCard, +} from '@backstage/plugin-user-settings'; + +import Grid from '@mui/material/Grid'; + +import { InfoCard } from './InfoCard'; + +export const GeneralPage = () => { + return ( + + + + + + + + + + + + + + + ); +}; diff --git a/packages/app/src/modules/user-settings/InfoCard.test.tsx b/packages/app/src/modules/user-settings/InfoCard.test.tsx new file mode 100644 index 0000000000..58589e53c6 --- /dev/null +++ b/packages/app/src/modules/user-settings/InfoCard.test.tsx @@ -0,0 +1,185 @@ +import { configApiRef } from '@backstage/core-plugin-api'; +import { + mockApis, + renderInTestApp, + TestApiProvider, +} from '@backstage/frontend-test-utils'; + +import { userEvent } from '@testing-library/user-event'; + +import { InfoCard } from './InfoCard'; + +describe('InfoCard', () => { + it('should render essential versions by default', async () => { + const mockConfig = mockApis.config({ + data: { + buildInfo: {}, + }, + }); + const renderResult = await renderInTestApp( + + + , + ); + expect(renderResult.getByText(/RHDH Version/)).toBeInTheDocument(); + expect(renderResult.getByText(/Backstage Version/)).toBeInTheDocument(); + }); + + it('should hide the build time by default and show it on click', async () => { + const mockConfig = mockApis.config({ + data: { + buildInfo: {}, + }, + }); + const renderResult = await renderInTestApp( + + + , + ); + expect(renderResult.queryByText(/Last Commit/)).toBeNull(); + await userEvent.click(renderResult.getByText(/RHDH Version/)); + expect(renderResult.getByText(/Last Commit/)).toBeInTheDocument(); + }); + + it('should render the customized values when build info is configured', async () => { + const mockConfig = mockApis.config({ + data: { + buildInfo: { + title: 'RHDH Build info', + card: { + 'TechDocs builder': 'local', + 'Authentication provider': 'Github', + RBAC: 'disabled', + }, + }, + }, + }); + const renderResult = await renderInTestApp( + + + , + ); + expect(renderResult.queryByText(/TechDocs builder/)).toBeInTheDocument(); + expect( + renderResult.queryByText(/Authentication provider/), + ).toBeInTheDocument(); + await userEvent.click(renderResult.getByText(/TechDocs builder/)); + expect(renderResult.getByText(/RBAC/)).toBeInTheDocument(); + expect(renderResult.queryByText(/Last Commit/)).not.toBeInTheDocument(); + }); + + it('should append the customized values along with RHDH versions when build info is configured with `full` set to false', async () => { + const mockConfig = mockApis.config({ + data: { + buildInfo: { + title: 'RHDH Build info', + card: { + 'TechDocs builder': 'local', + 'Authentication provider': 'Github', + RBAC: 'disabled', + }, + overrideBuildInfo: false, + }, + }, + }); + const renderResult = await renderInTestApp( + + + , + ); + expect(renderResult.queryByText(/TechDocs builder/)).toBeInTheDocument(); + expect( + renderResult.queryByText(/Authentication provider/), + ).toBeInTheDocument(); + await userEvent.click(renderResult.getByText(/TechDocs builder/)); + expect(renderResult.getByText(/RBAC/)).toBeInTheDocument(); + expect(renderResult.queryByText(/Last Commit/)).toBeInTheDocument(); + expect(renderResult.queryByText(/RHDH Version/)).toBeInTheDocument(); + }); + + it('should display only the customized values when build info is configured with full set to true, without appending RHDH versions', async () => { + const mockConfig = mockApis.config({ + data: { + buildInfo: { + title: 'RHDH Build info', + card: { + 'TechDocs builder': 'local', + 'Authentication provider': 'Github', + RBAC: 'disabled', + }, + overrideBuildInfo: true, + }, + }, + }); + const renderResult = await renderInTestApp( + + + , + ); + expect(renderResult.queryByText(/TechDocs builder/)).toBeInTheDocument(); + expect( + renderResult.queryByText(/Authentication provider/), + ).toBeInTheDocument(); + await userEvent.click(renderResult.getByText(/TechDocs builder/)); + expect(renderResult.getByText(/RBAC/)).toBeInTheDocument(); + expect(renderResult.queryByText(/Last Commit/)).not.toBeInTheDocument(); + }); + + it('should fallback to default json if the customized card value is empty', async () => { + const mockConfig = mockApis.config({ + data: { + buildInfo: { + title: 'RHDH Build info', + card: undefined, + }, + }, + }); + const renderResult = await renderInTestApp( + + + , + ); + expect( + renderResult.queryByText(/TechDocs builder/), + ).not.toBeInTheDocument(); + expect(renderResult.getByText(/RHDH Version/)).toBeInTheDocument(); + expect(renderResult.getByText(/Backstage Version/)).toBeInTheDocument(); + }); + + // Mirrors e2e-tests/.../user-settings-info-card.spec.ts: asserts the card + // title and the "key: value" lines (collapsed shows the first two, expanding + // reveals the rest) — behavior the key-only assertions above did not cover. + it('renders the build info title and key/value lines, revealing the rest on expand', async () => { + const mockConfig = mockApis.config({ + data: { + buildInfo: { + title: 'RHDH Build info', + card: { + 'TechDocs builder': 'local', + 'Authentication provider': 'Github', + RBAC: 'disabled', + }, + }, + }, + }); + const renderResult = await renderInTestApp( + + + , + ); + + expect(renderResult.getByText(/RHDH Build info/)).toBeInTheDocument(); + expect( + renderResult.getByText(/TechDocs builder: local/), + ).toBeInTheDocument(); + expect( + renderResult.getByText(/Authentication provider: Github/), + ).toBeInTheDocument(); + // Collapsed view shows only the first two entries. + expect(renderResult.queryByText(/RBAC: disabled/)).not.toBeInTheDocument(); + + await userEvent.click(renderResult.getByText(/TechDocs builder: local/)); + + expect(renderResult.getByText(/RBAC: disabled/)).toBeInTheDocument(); + }); +}); diff --git a/packages/app/src/modules/user-settings/InfoCard.tsx b/packages/app/src/modules/user-settings/InfoCard.tsx new file mode 100644 index 0000000000..030f44b5da --- /dev/null +++ b/packages/app/src/modules/user-settings/InfoCard.tsx @@ -0,0 +1,164 @@ +import { KeyboardEvent, MouseEvent, useState } from 'react'; + +import { + InfoCard as BSInfoCard, + CopyTextButton, +} from '@backstage/core-components'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; + +import UnfoldLessIcon from '@mui/icons-material/UnfoldLess'; +import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore'; +import IconButton from '@mui/material/IconButton'; +import Typography from '@mui/material/Typography'; + +import buildMetadata from '../../build-metadata.json'; +import { useTranslation } from '../../hooks/useTranslation'; +import { BuildInfo } from './types'; + +export const InfoCard = () => { + const config = useApi(configApiRef); + const { t } = useTranslation(); + const buildInfo: BuildInfo | undefined = config.getOptional('buildInfo'); + + const [showBuildInformation, setShowBuildInformation] = useState( + () => + localStorage.getItem('rhdh-infocard-show-build-information') === 'true', + ); + + const toggleBuildInformation = () => { + setShowBuildInformation(!showBuildInformation); + try { + if (showBuildInformation) { + localStorage.removeItem('rhdh-infocard-show-build-information'); + } else { + localStorage.setItem('rhdh-infocard-show-build-information', 'true'); + } + } catch (e) { + // ignore + } + }; + + const getTitle = () => { + const defaultTitle = buildInfo?.title ?? buildMetadata?.title; + + // If titleKey is provided, use translation + if (buildInfo?.titleKey) { + return t(buildInfo.titleKey as any, { + defaultValue: defaultTitle, + }); + } + // If no title but titleKey in metadata, use that translation + if (!buildInfo?.title && buildMetadata?.titleKey) { + return t(buildMetadata.titleKey as any, { + defaultValue: buildMetadata?.title, + }); + } + + // Fall back to title or default + return defaultTitle; + }; + + const title = getTitle(); + + let clipboardText = title; + const buildDetails = Object.entries( + buildInfo?.full === false || // make it backward compatible with previous `full` config option + buildInfo?.overrideBuildInfo === false + ? { ...buildInfo?.card, ...buildMetadata?.card } + : (buildInfo?.card ?? buildMetadata?.card), + ).map(([key, value]) => `${key}: ${value}`); + if (buildDetails?.length) { + clipboardText += '\n\n'; + buildDetails.forEach(text => { + clipboardText += `${text}\n`; + }); + } + + const filteredContent = () => { + if (buildInfo?.card) { + return buildDetails.slice(0, 2); + } + return buildDetails.filter( + text => + text.startsWith('RHDH Version') || text.startsWith('Backstage Version'), + ); + }; + + const filteredCards = showBuildInformation ? buildDetails : filteredContent(); + // Ensure that we show always some information + const versionInfo = + filteredCards.length > 0 ? filteredCards.join('\n') : buildDetails[0]; + + /** + * Show all build information and automatically select them + * when the user selects the version with the mouse. + */ + const onMouseUp = (event: MouseEvent) => { + if (!showBuildInformation) { + setShowBuildInformation(true); + window.getSelection()?.selectAllChildren(event.target as Node); + } + }; + + /** + * Show all build information and automatically select them + * when the user selects the version with the keyboard (tab) + * and presses the space key or the Ctrl+C key combination. + */ + const onKeyDown = (event: KeyboardEvent) => { + if ( + event.key === ' ' || + (event.key === 'c' && event.ctrlKey) || + (event.key === 'C' && event.ctrlKey) + ) { + setShowBuildInformation(true); + window.getSelection()?.selectAllChildren(event.target as Node); + } + }; + + return ( + +

+ + + {showBuildInformation ? : } + +
+ + } + > + + {versionInfo} + + + ); +}; diff --git a/packages/app/src/modules/user-settings/index.tsx b/packages/app/src/modules/user-settings/index.tsx new file mode 100644 index 0000000000..a8f4b6fe79 --- /dev/null +++ b/packages/app/src/modules/user-settings/index.tsx @@ -0,0 +1,28 @@ +import { + createFrontendModule, + SubPageBlueprint, +} from '@backstage/frontend-plugin-api'; + +/** + * Overrides the upstream `sub-page:user-settings/general` extension to add + * the RHDH build-metadata `InfoCard` alongside the default profile, + * appearance, and identity cards. + * + * This module is intentionally kept local to `app` rather than upstreamed, + * since upstream NFS does not currently expose card slots on the General + * settings sub-page. See docs/adr/0001-app-next-sidebar-module-location.md + * for the equivalent rationale applied to the nav module. + */ +const userSettingsGeneral = SubPageBlueprint.make({ + name: 'general', + params: { + path: 'general', + title: 'General', + loader: () => import('./GeneralPage').then(m => ), + }, +}); + +export const userSettingsGeneralModule = createFrontendModule({ + pluginId: 'user-settings', + extensions: [userSettingsGeneral], +}); diff --git a/packages/app/src/modules/user-settings/types.ts b/packages/app/src/modules/user-settings/types.ts new file mode 100644 index 0000000000..f8dcfd2fb6 --- /dev/null +++ b/packages/app/src/modules/user-settings/types.ts @@ -0,0 +1,7 @@ +export type BuildInfo = { + title?: string; + titleKey?: string; + card: { [key: string]: string }; + full?: boolean; + overrideBuildInfo?: boolean; +}; diff --git a/packages/app/src/setupTests.ts b/packages/app/src/setupTests.ts index 7b0828bfa8..8f2609b7b3 100644 --- a/packages/app/src/setupTests.ts +++ b/packages/app/src/setupTests.ts @@ -1 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom'; diff --git a/packages/app-next/src/translations/translationsModule.ts b/packages/app/src/translations/translationsModule.ts similarity index 96% rename from packages/app-next/src/translations/translationsModule.ts rename to packages/app/src/translations/translationsModule.ts index 783b3a6d3a..53f12994e6 100644 --- a/packages/app-next/src/translations/translationsModule.ts +++ b/packages/app/src/translations/translationsModule.ts @@ -13,7 +13,7 @@ import { userSettingsTranslations } from './user-settings/user-settings'; * RHDH core translations (`rhdhTranslationRef`, id `rhdh`) plus overrides for * the catalog, catalog-import, scaffolder, search, user-settings, and * core-components plugin translation resources — ported from - * `packages/app/src/translations`. Locales: de, es, fr, it, ja. + * the legacy OFS frontend translations. Locales: de, es, fr, it, ja. */ export const rhdhTranslationsModule = createFrontendModule({ pluginId: 'app', diff --git a/packages/backend/package.json b/packages/backend/package.json index 3eb46b07fb..ea841b9b57 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -8,8 +8,8 @@ "role": "backend" }, "scripts": { - "start": "backstage-cli package start --require ./src/instrumentation.js", - "start:next": "APP_CONFIG_app_packageName=app-next ENABLE_STANDARD_MODULE_FEDERATION=true backstage-cli package start --require ./src/instrumentation.js", + "start": "ENABLE_STANDARD_MODULE_FEDERATION=true backstage-cli package start --require ./src/instrumentation.js", + "start:legacy": "APP_CONFIG_app_packageName=app-legacy backstage-cli package start --require ./src/instrumentation.js", "start:prometheus": "docker run --mount type=bind,source=./prometheus.yml,destination=/etc/prometheus/prometheus.yml --publish published=9090,target=9090,protocol=tcp prom/prometheus", "build": "backstage-cli package build", "lint:check": "backstage-cli package lint", @@ -77,7 +77,6 @@ "@red-hat-developer-hub/backstage-plugin-translations-backend": "0.3.1", "@red-hat-developer-hub/cli-module-install-dynamic-plugins": "0.3.0", "app": "workspace:*", - "app-next": "workspace:*", "better-sqlite3": "12.11.1", "global-agent": "3.0.0", "jose": "5.10.0", diff --git a/packages/backend/src/schemaLocator.ts b/packages/backend/src/schemaLocator.ts index 8829d16a78..39183958a7 100644 --- a/packages/backend/src/schemaLocator.ts +++ b/packages/backend/src/schemaLocator.ts @@ -14,7 +14,7 @@ import type { ScannedPluginPackage } from '@backstage/backend-dynamic-feature-se * and backend packages, falling back to the legacy per-platform path written * by the old janus-idp/cli (Scalprum) toolchain. * - * The legacy fallback should be removed entirely once OFS (`packages/app`) + * The legacy fallback should be removed entirely once the legacy OFS frontend * is deleted in RHIDP-15078/RHIDP-15079. */ export function schemaLocator(pluginPackage: ScannedPluginPackage): string { diff --git a/yarn.lock b/yarn.lock index 33163d32cc..8e2606db46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15870,73 +15870,66 @@ __metadata: languageName: node linkType: hard -"app-next@workspace:*, app-next@workspace:packages/app-next": +"app-legacy@workspace:packages/app-legacy": version: 0.0.0-use.local - resolution: "app-next@workspace:packages/app-next" + resolution: "app-legacy@workspace:packages/app-legacy" dependencies: + "@backstage-community/plugin-rbac-common": "npm:1.26.1" "@backstage/app-defaults": "npm:1.7.9" "@backstage/catalog-model": "npm:1.9.0" "@backstage/cli": "npm:0.36.3" "@backstage/cli-defaults": "npm:0.1.3" "@backstage/config": "npm:1.3.8" "@backstage/core-app-api": "npm:1.20.2" - "@backstage/core-compat-api": "npm:0.5.12" "@backstage/core-components": "npm:0.18.11" "@backstage/core-plugin-api": "npm:1.12.7" - "@backstage/frontend-app-api": "npm:0.16.4" - "@backstage/frontend-defaults": "npm:0.5.3" - "@backstage/frontend-dynamic-feature-loader": "npm:0.1.13" - "@backstage/frontend-plugin-api": "npm:0.17.2" "@backstage/frontend-test-utils": "npm:0.6.1" "@backstage/integration-react": "npm:1.2.19" - "@backstage/plugin-app": "npm:0.5.0" - "@backstage/plugin-app-react": "npm:0.2.4" - "@backstage/plugin-app-visualizer": "npm:0.2.5" - "@backstage/plugin-auth-react": "npm:0.1.28" + "@backstage/plugin-api-docs": "npm:0.14.2" "@backstage/plugin-catalog": "npm:2.0.6" "@backstage/plugin-catalog-common": "npm:1.1.10" "@backstage/plugin-catalog-graph": "npm:0.6.5" "@backstage/plugin-catalog-import": "npm:0.13.14" "@backstage/plugin-catalog-react": "npm:3.1.0" - "@backstage/plugin-catalog-unprocessed-entities": "npm:0.2.32" + "@backstage/plugin-org": "npm:0.7.5" "@backstage/plugin-permission-react": "npm:0.5.2" "@backstage/plugin-scaffolder": "npm:1.38.0" "@backstage/plugin-scaffolder-react": "npm:2.0.1" "@backstage/plugin-search": "npm:1.7.5" - "@backstage/plugin-search-common": "npm:1.2.24" "@backstage/plugin-search-react": "npm:1.11.5" "@backstage/plugin-user-settings": "npm:0.9.4" "@backstage/test-utils": "npm:1.7.19" "@backstage/theme": "npm:0.7.3" "@backstage/ui": "npm:0.16.0" - "@material-ui/core": "npm:4.12.4" - "@material-ui/icons": "npm:4.11.3" - "@material-ui/lab": "npm:4.0.0-alpha.61" + "@emotion/react": "npm:11.14.0" + "@emotion/styled": "npm:11.14.1" + "@janus-idp/cli": "patch:@janus-idp/cli@npm%3A3.7.0#~/.yarn/patches/@janus-idp-cli-npm-3.7.0-f225cddfdb.patch" "@mui/icons-material": "npm:5.18.0" "@mui/material": "npm:5.18.0" - "@octokit/rest": "npm:19.0.13" - "@red-hat-developer-hub/backstage-plugin-app-auth": "npm:0.1.0" - "@red-hat-developer-hub/backstage-plugin-app-integrations": "npm:0.1.0" - "@red-hat-developer-hub/backstage-plugin-app-react": "npm:0.1.0" + "@mui/styled-engine": "npm:5.18.0" "@red-hat-developer-hub/backstage-plugin-theme": "npm:0.14.7" + "@red-hat-developer-hub/backstage-plugin-translations": "npm:0.3.1" + "@red-hat-developer-hub/plugin-utils": "workspace:*" + "@scalprum/core": "npm:0.10.0" + "@scalprum/react-core": "npm:0.13.0" + "@scalprum/react-test-utils": "npm:0.3.0" "@testing-library/dom": "npm:9.3.4" "@testing-library/jest-dom": "npm:6.9.1" "@testing-library/react": "npm:14.3.1" + "@testing-library/react-hooks": "npm:8.0.1" "@testing-library/user-event": "npm:14.6.1" - "@types/jquery": "npm:3.5.34" + "@types/node": "npm:24.13.2" "@types/react": "npm:18.3.31" "@types/react-dom": "npm:18.3.7" - "@types/zen-observable": "npm:0.8.7" - cross-env: "npm:7.0.3" - history: "npm:5.3.0" + eslint-plugin-unused-imports: "npm:4.4.1" + lodash: "npm:4.18.1" + prettier: "npm:3.8.4" react: "npm:18.3.1" react-dom: "npm:18.3.1" - react-router: "npm:6.30.4" react-router-dom: "npm:6.30.4" react-use: "npm:17.6.1" tss-react: "npm:4.9.21" typescript: "npm:5.9.3" - zen-observable: "npm:0.10.0" languageName: unknown linkType: soft @@ -15944,62 +15937,69 @@ __metadata: version: 0.0.0-use.local resolution: "app@workspace:packages/app" dependencies: - "@backstage-community/plugin-rbac-common": "npm:1.26.1" "@backstage/app-defaults": "npm:1.7.9" "@backstage/catalog-model": "npm:1.9.0" "@backstage/cli": "npm:0.36.3" "@backstage/cli-defaults": "npm:0.1.3" "@backstage/config": "npm:1.3.8" "@backstage/core-app-api": "npm:1.20.2" + "@backstage/core-compat-api": "npm:0.5.12" "@backstage/core-components": "npm:0.18.11" "@backstage/core-plugin-api": "npm:1.12.7" + "@backstage/frontend-app-api": "npm:0.16.4" + "@backstage/frontend-defaults": "npm:0.5.3" + "@backstage/frontend-dynamic-feature-loader": "npm:0.1.13" + "@backstage/frontend-plugin-api": "npm:0.17.2" "@backstage/frontend-test-utils": "npm:0.6.1" "@backstage/integration-react": "npm:1.2.19" - "@backstage/plugin-api-docs": "npm:0.14.2" + "@backstage/plugin-app": "npm:0.5.0" + "@backstage/plugin-app-react": "npm:0.2.4" + "@backstage/plugin-app-visualizer": "npm:0.2.5" + "@backstage/plugin-auth-react": "npm:0.1.28" "@backstage/plugin-catalog": "npm:2.0.6" "@backstage/plugin-catalog-common": "npm:1.1.10" "@backstage/plugin-catalog-graph": "npm:0.6.5" "@backstage/plugin-catalog-import": "npm:0.13.14" "@backstage/plugin-catalog-react": "npm:3.1.0" - "@backstage/plugin-org": "npm:0.7.5" + "@backstage/plugin-catalog-unprocessed-entities": "npm:0.2.32" "@backstage/plugin-permission-react": "npm:0.5.2" "@backstage/plugin-scaffolder": "npm:1.38.0" "@backstage/plugin-scaffolder-react": "npm:2.0.1" "@backstage/plugin-search": "npm:1.7.5" + "@backstage/plugin-search-common": "npm:1.2.24" "@backstage/plugin-search-react": "npm:1.11.5" "@backstage/plugin-user-settings": "npm:0.9.4" "@backstage/test-utils": "npm:1.7.19" "@backstage/theme": "npm:0.7.3" "@backstage/ui": "npm:0.16.0" - "@emotion/react": "npm:11.14.0" - "@emotion/styled": "npm:11.14.1" - "@janus-idp/cli": "patch:@janus-idp/cli@npm%3A3.7.0#~/.yarn/patches/@janus-idp-cli-npm-3.7.0-f225cddfdb.patch" + "@material-ui/core": "npm:4.12.4" + "@material-ui/icons": "npm:4.11.3" + "@material-ui/lab": "npm:4.0.0-alpha.61" "@mui/icons-material": "npm:5.18.0" "@mui/material": "npm:5.18.0" - "@mui/styled-engine": "npm:5.18.0" + "@octokit/rest": "npm:19.0.13" + "@red-hat-developer-hub/backstage-plugin-app-auth": "npm:0.1.0" + "@red-hat-developer-hub/backstage-plugin-app-integrations": "npm:0.1.0" + "@red-hat-developer-hub/backstage-plugin-app-react": "npm:0.1.0" "@red-hat-developer-hub/backstage-plugin-theme": "npm:0.14.7" - "@red-hat-developer-hub/backstage-plugin-translations": "npm:0.3.1" - "@red-hat-developer-hub/plugin-utils": "workspace:*" - "@scalprum/core": "npm:0.10.0" - "@scalprum/react-core": "npm:0.13.0" - "@scalprum/react-test-utils": "npm:0.3.0" "@testing-library/dom": "npm:9.3.4" "@testing-library/jest-dom": "npm:6.9.1" "@testing-library/react": "npm:14.3.1" - "@testing-library/react-hooks": "npm:8.0.1" "@testing-library/user-event": "npm:14.6.1" - "@types/node": "npm:24.13.2" + "@types/jquery": "npm:3.5.34" "@types/react": "npm:18.3.31" "@types/react-dom": "npm:18.3.7" - eslint-plugin-unused-imports: "npm:4.4.1" - lodash: "npm:4.18.1" - prettier: "npm:3.8.4" + "@types/zen-observable": "npm:0.8.7" + cross-env: "npm:7.0.3" + history: "npm:5.3.0" react: "npm:18.3.1" react-dom: "npm:18.3.1" + react-router: "npm:6.30.4" react-router-dom: "npm:6.30.4" react-use: "npm:17.6.1" tss-react: "npm:4.9.21" typescript: "npm:5.9.3" + zen-observable: "npm:0.10.0" languageName: unknown linkType: soft @@ -16645,7 +16645,6 @@ __metadata: "@types/express": "npm:4.17.25" "@types/global-agent": "npm:2.1.3" app: "workspace:*" - app-next: "workspace:*" better-sqlite3: "npm:12.11.1" global-agent: "npm:3.0.0" jose: "npm:5.10.0" From d4e3db049e98e80e7b68f728f7b7161a19563ef9 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Fri, 7 Aug 2026 12:20:23 -0400 Subject: [PATCH 04/48] chore: initial e2e test updates Signed-off-by: Frank Kong Assisted-By: Cursor rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- .github/workflows/e2e-cluster-free.yaml | 2 +- app-config.local-e2e.yaml | 2 +- build/containerfiles/Containerfile | 1 - ...migrating-config-to-new-frontend-system.md | 19 ++++------------- docs/e2e-tests/local-e2e-harness.md | 21 ++++++++----------- e2e-tests/playwright.legacy-local.config.ts | 18 +++++++++------- .../e2e/plugins/tmp-infocard-check.spec.ts | 16 ++++++++++++++ .../playwright/utils/ui-helper/navigation.ts | 4 ++-- 8 files changed, 43 insertions(+), 40 deletions(-) create mode 100644 e2e-tests/playwright/e2e/plugins/tmp-infocard-check.spec.ts diff --git a/.github/workflows/e2e-cluster-free.yaml b/.github/workflows/e2e-cluster-free.yaml index b92c997b5b..7f12e06523 100644 --- a/.github/workflows/e2e-cluster-free.yaml +++ b/.github/workflows/e2e-cluster-free.yaml @@ -83,7 +83,7 @@ jobs: # after a source build, so we install the OCI-published builds instead. run: ./e2e-tests/local-harness/populate.sh - - name: Run cluster-free E2E (legacy app) + - name: Run cluster-free E2E (NFS app) working-directory: ./e2e-tests run: yarn e2e:legacy-local diff --git a/app-config.local-e2e.yaml b/app-config.local-e2e.yaml index 9d0ad3b1bf..07c63bdd64 100644 --- a/app-config.local-e2e.yaml +++ b/app-config.local-e2e.yaml @@ -1,4 +1,4 @@ -# Config overlay for the cluster-free local E2E harness (legacy `packages/app`, Tier B). +# Config overlay for the cluster-free local E2E harness (NFS `packages/app`, Tier B). # # Layered on top of app-config.yaml and app-config.dynamic-plugins.yaml to run # Playwright E2E without an OpenShift/Kubernetes cluster or container images: diff --git a/build/containerfiles/Containerfile b/build/containerfiles/Containerfile index fb8d6c0d3f..12d8e0712c 100644 --- a/build/containerfiles/Containerfile +++ b/build/containerfiles/Containerfile @@ -73,7 +73,6 @@ COPY $EXTERNAL_SOURCE_NESTED/packages/theme-wrapper/package.json ./packages/them COPY $EXTERNAL_SOURCE_NESTED/packages/plugin-utils/package.json ./packages/plugin-utils/package.json COPY $EXTERNAL_SOURCE_NESTED/packages/backend/package.json ./packages/backend/package.json COPY $EXTERNAL_SOURCE_NESTED/packages/app/package.json ./packages/app/package.json -COPY $EXTERNAL_SOURCE_NESTED/packages/app-next/package.json ./packages/app-next/package.json COPY $EXTERNAL_SOURCE_NESTED/package.json ./package.json # END COPY package.json files diff --git a/docs/dynamic-plugins/migrating-config-to-new-frontend-system.md b/docs/dynamic-plugins/migrating-config-to-new-frontend-system.md index ecd586d7f0..c1afb10dbd 100644 --- a/docs/dynamic-plugins/migrating-config-to-new-frontend-system.md +++ b/docs/dynamic-plugins/migrating-config-to-new-frontend-system.md @@ -6,31 +6,20 @@ This guide helps **operators and platform administrators** customize Red Hat Dev > > **Plugin authors** → [Migrating Plugins to the New Frontend System](migrating-plugins-to-new-frontend-system.md). -## Transition: the new frontend system is not the default yet +## Default: new frontend system (NFS) -RHDH still ships the legacy `app` frontend package by default. The new frontend system lives in the `app-next` package and will become the default after the app-shell switch. Until then, enable **both** of the following on your RHDH **backend** deployment (OpenShift, Helm, Operator, [rhdh-local](https://github.com/redhat-developer/rhdh-local), or any environment where the backend runs as a container): +RHDH ships the Backstage new frontend system (`packages/app`) by default. Enable standard Module Federation on the **backend** deployment so dynamic frontend plugins load correctly (OpenShift, Helm, Operator, [rhdh-local](https://github.com/redhat-developer/rhdh-local), or any environment where the backend runs as a container): | Setting | How to apply | Purpose | | --- | --- | --- | -| `app.packageName: app-next` | Environment variable `APP_CONFIG_app_packageName=app-next`, **or** in `app-config.yaml` under `app.packageName` | Tells the app backend to serve the `app-next` frontend (new frontend system) instead of `app`. | -| `ENABLE_STANDARD_MODULE_FEDERATION=true` | Environment variable on the backend container only | Enables the backend to serve standard Module Federation assets for dynamic frontend plugins. Without this, RHDH disables that service because the legacy frontend does not use it. | +| `ENABLE_STANDARD_MODULE_FEDERATION=true` | Environment variable on the backend container only | Enables the backend to serve standard Module Federation assets for dynamic frontend plugins. Without this, RHDH disables that service. | -Example environment variables for the RHDH backend pod or deployment: +Example environment variable for the RHDH backend pod or deployment: ```bash -APP_CONFIG_app_packageName=app-next ENABLE_STANDARD_MODULE_FEDERATION=true ``` -Equivalent `app-config` fragment (you still need `ENABLE_STANDARD_MODULE_FEDERATION` in the environment): - -```yaml -app: - packageName: app-next -``` - -These requirements are temporary. Once RHDH completes the switch to `app-next`, they will become the default and this transition note can be removed. - ## Who should read this - RHDH administrators who edit `dynamic-plugins.yaml`, Helm values, or Operator configuration. diff --git a/docs/e2e-tests/local-e2e-harness.md b/docs/e2e-tests/local-e2e-harness.md index 54fd47e05d..7369bc88b2 100644 --- a/docs/e2e-tests/local-e2e-harness.md +++ b/docs/e2e-tests/local-e2e-harness.md @@ -7,13 +7,13 @@ backend dynamic-plugin loader from RHIDP-13508. ## Goal Run real Playwright E2E against RHDH **without** an OpenShift/Kubernetes cluster or -container images — a single `run` that boots the backend and the legacy frontend dev +container images — a single `run` that boots the backend and the NFS frontend dev server in-process and drives a browser against them. -The harness targets the legacy frontend (`packages/app`, Tier B): it is what RHDH ships +The harness targets the NFS frontend (`packages/app`, Tier B): it is what RHDH ships today, and **the existing Playwright specs already target it**, so they run unmodified. -Dynamic frontend plugins load through Scalprum exactly as in-cluster (the legacy -`scalprum-backend` serves the plugin config by default). +Dynamic frontend plugins load via standard Module Federation when the backend is started +with `ENABLE_STANDARD_MODULE_FEDERATION=true`. The guest-auth + in-memory-SQLite overlay `app-config.local-e2e.yaml` is layered on top of `app-config.yaml`. Guest sign-in must be configured explicitly — the auth backend @@ -126,15 +126,12 @@ registry, ghcr), then runs `yarn e2e:legacy-local`. No cluster or container imag built. It triggers on `e2e-tests/**` and `app-config*.yaml` changes; the scope can widen to `packages/app/**` / `packages/backend/**` once it is proven stable. -## Why the legacy app, not app-next +## NFS frontend (`packages/app`) -The harness targets the legacy app because **dynamic frontend plugins do not load on -`packages/app-next` yet**: app-next's `dynamicFrontendFeaturesLoader()` fetches Module -Federation remotes from the backend, but that endpoint is no-op'd unless -`ENABLE_STANDARD_MODULE_FEDERATION=true`, and even then RHDH's exported dynamic frontend -plugins do not contain standard MF assets (see `packages/backend/src/index.ts`). Until -that lands upstream, app-next can only exercise core/static plugin UIs. An app-next -harness is tracked as a follow-up (RHIDP-13501 / spike RHIDP-15075). +The harness targets the NFS frontend (`packages/app`). The backend boots with +`ENABLE_STANDARD_MODULE_FEDERATION=true` so dynamic frontend plugins load via Module +Federation remotes from the backend (see `packages/backend/src/index.ts` and +`playwright.legacy-local.config.ts`). ## vs. rhdh-local diff --git a/e2e-tests/playwright.legacy-local.config.ts b/e2e-tests/playwright.legacy-local.config.ts index 53a36f599b..ea1380e502 100644 --- a/e2e-tests/playwright.legacy-local.config.ts +++ b/e2e-tests/playwright.legacy-local.config.ts @@ -3,12 +3,12 @@ import { resolve } from "path"; import { defineConfig, devices } from "@playwright/test"; /** - * Cluster-free local E2E harness for the legacy frontend (`packages/app`) — Tier B. + * Cluster-free local E2E harness for the NFS frontend (`packages/app`) — Tier B. * * RHIDP-13501 (E2E Test Optimization). Runs the EXISTING Playwright specs against a * production-faithful RHDH instance with dynamic plugins loaded, without an * OpenShift/Kubernetes cluster or container images. Playwright boots the backend and - * the legacy app dev server itself and drives the browser against them. + * the NFS app dev server itself and drives the browser against them. * * # one-time: populate dynamic-plugins-root (same script CI uses — OCI, no build; * # alternatives in docs/e2e-tests/local-e2e-harness.md): @@ -49,7 +49,7 @@ export default defineConfig({ // spec file here. Validated so far: the full guest-signin spec (home page via the // dynamic-home-page OCI plugin; Settings/Sign-out via the global-header OCI plugin // with its canonical pluginConfig), the learning-paths spec (static fallback - // data bundled with packages/app), and the instance health check (/healthcheck is + // data bundled with the legacy OFS frontend), and the instance health check (/healthcheck is // proxied to the backend by the app dev server — see packages/app package.json — // mirroring the single-origin production container). testMatch: [ @@ -61,6 +61,7 @@ export default defineConfig({ "e2e/plugins/frontend/sidebar.spec.ts", "e2e/settings.spec.ts", "e2e/plugins/user-settings-info-card.spec.ts", + "e2e/plugins/tmp-infocard-check.spec.ts", "e2e/plugins/application-provider.spec.ts", "e2e/plugins/application-listener.spec.ts", ], @@ -94,10 +95,10 @@ export default defineConfig({ expect: { timeout: 15 * 1000, }, - // backstage-cli / janus-cli live in the repo-root node_modules/.bin, which yarn does - // not surface for these workspaces, so both CLIs are invoked directly with the root - // .bin prepended to PATH and run from their package directory. The backend command - // mirrors packages/backend's `start` script (--require instrumentation) — keep in sync. + // backstage-cli lives in the repo-root node_modules/.bin, which yarn does not surface + // for these workspaces, so the CLI is invoked directly with the root .bin prepended + // to PATH and run from each package directory. The backend command mirrors + // packages/backend's `start` script (--require instrumentation) — keep in sync. webServer: [ { command: `backstage-cli package start --require ./src/instrumentation.js ${sharedConfigArgs}`, @@ -106,6 +107,7 @@ export default defineConfig({ ...process.env, PATH: pathWithRepoBin, NODE_OPTIONS: "--no-node-snapshot", + ENABLE_STANDARD_MODULE_FEDERATION: "true", }, url: backendReadiness, reuseExistingServer: !isCI, @@ -114,7 +116,7 @@ export default defineConfig({ stderr: "pipe", }, { - command: `janus-cli package start ${sharedConfigArgs}`, + command: `backstage-cli package start ${sharedConfigArgs}`, cwd: "../packages/app", env: { ...process.env, PATH: pathWithRepoBin }, url: frontendUrl, diff --git a/e2e-tests/playwright/e2e/plugins/tmp-infocard-check.spec.ts b/e2e-tests/playwright/e2e/plugins/tmp-infocard-check.spec.ts new file mode 100644 index 0000000000..09c315dfe2 --- /dev/null +++ b/e2e-tests/playwright/e2e/plugins/tmp-infocard-check.spec.ts @@ -0,0 +1,16 @@ +import { test, expect } from "@playwright/test"; + +import { Common } from "../../utils/common"; + +test("tmp: settings general renders InfoCard", { tag: "@cluster-free" }, async ({ page }) => { + const common = new Common(page); + await common.loginAsGuest(); + + await page.goto("/settings/general"); + await expect(page.getByText("RHDH Build info")).toBeVisible({ timeout: 20000 }); + await expect(page.getByText("TechDocs builder: local")).toBeVisible(); + await expect(page.getByText("Authentication provider: Github")).toBeVisible(); + + await page.getByTitle("Show more").click(); + await expect(page.getByText("RBAC: disabled")).toBeVisible(); +}); diff --git a/e2e-tests/playwright/utils/ui-helper/navigation.ts b/e2e-tests/playwright/utils/ui-helper/navigation.ts index 14640a4070..07e5e8146b 100644 --- a/e2e-tests/playwright/utils/ui-helper/navigation.ts +++ b/e2e-tests/playwright/utils/ui-helper/navigation.ts @@ -198,7 +198,7 @@ export async function waitForSideBarVisible(page: Page) { } export async function openSidebar(page: Page, navBarText: string) { - // Legacy packages/app sidebar (cluster-free harness) uses plain nav anchors. + // Legacy OFS sidebar uses plain nav anchors. const legacyLink = page.locator(`nav a:has-text("${navBarText}")`).first(); try { await expect(legacyLink).toBeVisible({ timeout: 3_000 }); @@ -231,7 +231,7 @@ export async function openCatalogSidebar(page: Page, kind: string) { } export async function openSidebarButton(page: Page, navBarButtonLabel: string) { - // Legacy packages/app sidebar expands groups via aria-label buttons. + // Legacy OFS sidebar expands groups via aria-label buttons. const legacyButton = page.locator(`nav button[aria-label="${navBarButtonLabel}"]`); try { await expect(legacyButton).toBeVisible({ timeout: 3_000 }); From 2bdbf36061412f1691379ad7c20c8d8725492f4e Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Mon, 10 Aug 2026 16:03:05 -0400 Subject: [PATCH 05/48] chore: add missing backstage homepage plugin Signed-off-by: Frank Kong rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- package.json | 2 +- packages/app/package.json | 5 +- packages/app/src/App.tsx | 2 + packages/app/src/modules/nav/Sidebar.tsx | 1 + yarn.lock | 398 ++++++++++++++++++++++- 5 files changed, 403 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a82312b43b..81cfff18c6 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "prepare": "husky install", "start": "NODE_OPTIONS=--no-node-snapshot turbo run start --filter=backend", "dev:legacy": "NODE_OPTIONS=--no-node-snapshot turbo run start --filter=backend --filter=app-legacy", - "dev": "NODE_OPTIONS=--no-node-snapshot turbo run start --filter=backend --filter=app", + "dev": "NODE_OPTIONS=--no-node-snapshot ENABLE_STANDARD_MODULE_FEDERATION=true turbo run start --filter=backend --filter=app", "build": "turbo run build", "build:dockerfile": "bash ./scripts/update-Dockerfile.sh", "tsc": "turbo run tsc", diff --git a/packages/app/package.json b/packages/app/package.json index 6a310cf57e..10076d6f6d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -8,7 +8,9 @@ }, "proxy": [ { - "context": ["/healthcheck"], + "context": [ + "/healthcheck" + ], "target": "http://localhost:7007" } ], @@ -70,6 +72,7 @@ "@backstage/plugin-catalog-import": "0.13.14", "@backstage/plugin-catalog-react": "3.1.0", "@backstage/plugin-catalog-unprocessed-entities": "0.2.32", + "@backstage/plugin-home": "0.9.7", "@backstage/plugin-permission-react": "0.5.2", "@backstage/plugin-scaffolder": "1.38.0", "@backstage/plugin-scaffolder-react": "2.0.1", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 3342bc06b1..a288942ae2 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -5,6 +5,7 @@ import catalogPlugin from '@backstage/plugin-catalog/alpha'; import scaffolderPlugin from '@backstage/plugin-scaffolder/alpha'; import searchPlugin from '@backstage/plugin-search/alpha'; import userSettingsPlugin from '@backstage/plugin-user-settings/alpha'; +import homePagePlugin from '@backstage/plugin-home/alpha'; import { appAuthModule } from '@red-hat-developer-hub/backstage-plugin-app-auth/alpha'; import { appIntegrationsModule } from '@red-hat-developer-hub/backstage-plugin-app-integrations/alpha'; @@ -19,6 +20,7 @@ import { rhdhTranslationsModule } from './translations/translationsModule'; const app = createApp({ features: [ // Upstream Backstage plugins + homePagePlugin, appVisualizerPlugin, catalogPlugin, scaffolderPlugin, diff --git a/packages/app/src/modules/nav/Sidebar.tsx b/packages/app/src/modules/nav/Sidebar.tsx index 89625f2160..58bcc26f14 100644 --- a/packages/app/src/modules/nav/Sidebar.tsx +++ b/packages/app/src/modules/nav/Sidebar.tsx @@ -44,6 +44,7 @@ export const SidebarContent = NavContentBlueprint.make({ }> + {nav.take('page:home')} {nav.take('page:catalog')} {nav.take('page:scaffolder')} diff --git a/yarn.lock b/yarn.lock index 8e2606db46..23a809a518 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2810,6 +2810,21 @@ __metadata: languageName: node linkType: hard +"@backstage/catalog-client@npm:^1.16.1": + version: 1.16.1 + resolution: "@backstage/catalog-client@npm:1.16.1" + dependencies: + "@backstage/catalog-model": "npm:^1.9.0" + "@backstage/errors": "npm:^1.3.1" + "@backstage/filter-predicates": "npm:^0.1.4" + "@backstage/plugin-catalog-common": "npm:^1.1.10" + cross-fetch: "npm:^4.0.0" + lodash: "npm:^4.17.21" + uri-template: "npm:^2.0.0" + checksum: 10c0/4b00fbada78234dd7a17863cef6134193b6d0396888cbcbfcda397d7321c594826f4f092d6bd6ac725e32a91ac3baf44a53a3bab5645ac7ef7c7ee803388f64f + languageName: node + linkType: hard + "@backstage/catalog-model@npm:1.9.0, @backstage/catalog-model@npm:^1.7.7, @backstage/catalog-model@npm:^1.9.0": version: 1.9.0 resolution: "@backstage/catalog-model@npm:1.9.0" @@ -3371,6 +3386,31 @@ __metadata: languageName: node linkType: hard +"@backstage/core-compat-api@npm:^0.5.13": + version: 0.5.13 + resolution: "@backstage/core-compat-api@npm:0.5.13" + dependencies: + "@backstage/core-plugin-api": "npm:^1.12.8" + "@backstage/errors": "npm:^1.3.1" + "@backstage/filter-predicates": "npm:^0.1.4" + "@backstage/frontend-plugin-api": "npm:^0.17.3" + "@backstage/plugin-app-react": "npm:^0.2.5" + "@backstage/plugin-catalog-react": "npm:^3.2.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/63ad4d56aea675e6764488069922d091652e411e64fcf98618b00a71b32a300896312d6b15a22b5b97d8e253741c94bbcd0babc9f44b7a9f2bc6a14d74202b69 + languageName: node + linkType: hard + "@backstage/core-components@npm:0.18.11, @backstage/core-components@npm:^0.18.11, @backstage/core-components@npm:^0.18.8": version: 0.18.11 resolution: "@backstage/core-components@npm:0.18.11" @@ -3429,6 +3469,65 @@ __metadata: languageName: node linkType: hard +"@backstage/core-components@npm:^0.18.12": + version: 0.18.12 + resolution: "@backstage/core-components@npm:0.18.12" + dependencies: + "@backstage/config": "npm:^1.3.8" + "@backstage/core-plugin-api": "npm:^1.12.8" + "@backstage/errors": "npm:^1.3.1" + "@backstage/theme": "npm:^0.7.3" + "@backstage/ui": "npm:^0.17.0" + "@backstage/version-bridge": "npm:^1.0.12" + "@dagrejs/dagre": "npm:^1.1.4" + "@date-io/core": "npm:^1.3.13" + "@material-table/core": "npm:^3.1.0" + "@material-ui/core": "npm:^4.12.2" + "@material-ui/icons": "npm:^4.9.1" + "@material-ui/lab": "npm:4.0.0-alpha.61" + "@react-hookz/web": "npm:^24.0.0" + "@testing-library/react": "npm:^16.0.0" + "@types/react-sparklines": "npm:^1.7.0" + ansi-regex: "npm:^6.0.1" + classnames: "npm:^2.2.6" + csstype: "npm:^3.0.2" + d3-selection: "npm:^3.0.0" + d3-shape: "npm:^3.0.0" + d3-zoom: "npm:^3.0.0" + js-yaml: "npm:^4.2.0" + linkify-react: "npm:4.3.2" + linkifyjs: "npm:4.3.2" + lodash: "npm:^4.17.21" + parse5: "npm:^6.0.0" + qs: "npm:^6.15.2" + rc-progress: "npm:3.5.1" + react-full-screen: "npm:^1.1.1" + react-helmet: "npm:6.1.0" + react-hook-form: "npm:^7.12.2" + react-idle-timer: "npm:5.7.2" + react-markdown: "npm:^8.0.0" + react-sparklines: "npm:^1.7.0" + react-syntax-highlighter: "npm:^15.4.5" + react-use: "npm:^17.3.2" + react-virtualized-auto-sizer: "npm:^1.0.11" + react-window: "npm:^1.8.6" + rehype-raw: "npm:^6.0.0" + rehype-sanitize: "npm:^5.0.0" + remark-gfm: "npm:^3.0.1" + zen-observable: "npm:^0.10.0" + zod: "npm:^3.25.76 || ^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/fa94668cf0f8facac7512dc8b2fff70c3dceefcfee569ae2983aa67e72452b35120674f1123a91a965781ae27bd9fa3bdf8af22d88dc6c15cc8220013e500627 + languageName: node + linkType: hard + "@backstage/core-plugin-api@npm:1.12.7, @backstage/core-plugin-api@npm:^1.12.4, @backstage/core-plugin-api@npm:^1.12.7": version: 1.12.7 resolution: "@backstage/core-plugin-api@npm:1.12.7" @@ -3452,6 +3551,29 @@ __metadata: languageName: node linkType: hard +"@backstage/core-plugin-api@npm:^1.12.8": + version: 1.12.8 + resolution: "@backstage/core-plugin-api@npm:1.12.8" + dependencies: + "@backstage/config": "npm:^1.3.8" + "@backstage/errors": "npm:^1.3.1" + "@backstage/frontend-plugin-api": "npm:^0.17.3" + "@backstage/types": "npm:^1.2.2" + "@backstage/version-bridge": "npm:^1.0.12" + history: "npm:^5.0.0" + zod: "npm:^3.25.76 || ^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/87d99483edca1969a6d56f91b9dccdd1ec544f67f566c0abf15ecfc67984b0322c18128defa841de8fdaaab89be931e8d38a70b14359868056ddd458cbd283bd + languageName: node + linkType: hard + "@backstage/errors@npm:1.3.1, @backstage/errors@npm:^1.2.7, @backstage/errors@npm:^1.3.0, @backstage/errors@npm:^1.3.1": version: 1.3.1 resolution: "@backstage/errors@npm:1.3.1" @@ -3485,6 +3607,19 @@ __metadata: languageName: node linkType: hard +"@backstage/filter-predicates@npm:^0.1.4": + version: 0.1.4 + resolution: "@backstage/filter-predicates@npm:0.1.4" + dependencies: + "@backstage/config": "npm:^1.3.8" + "@backstage/errors": "npm:^1.3.1" + "@backstage/types": "npm:^1.2.2" + zod: "npm:^3.25.76 || ^4.0.0" + zod-validation-error: "npm:^5.0.0" + checksum: 10c0/0e54760800797c45df47f58f0c1138cd2667939f0d1a4b83c63466fed82b293ffc144751bf98684b2e61edcc111c1cb34ebf607f19befd361e9b8f061f04d1cc + languageName: node + linkType: hard + "@backstage/frontend-app-api@npm:0.16.4, @backstage/frontend-app-api@npm:^0.16.4": version: 0.16.4 resolution: "@backstage/frontend-app-api@npm:0.16.4" @@ -3605,6 +3740,30 @@ __metadata: languageName: node linkType: hard +"@backstage/frontend-plugin-api@npm:^0.17.3": + version: 0.17.3 + resolution: "@backstage/frontend-plugin-api@npm:0.17.3" + dependencies: + "@backstage/config": "npm:^1.3.8" + "@backstage/errors": "npm:^1.3.1" + "@backstage/filter-predicates": "npm:^0.1.4" + "@backstage/types": "npm:^1.2.2" + "@backstage/version-bridge": "npm:^1.0.12" + "@standard-schema/spec": "npm:^1.1.0" + zod: "npm:^4.0.0" + zod-to-json-schema: "npm:^3.25.1" + 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/d321dd4e37d655a2b1e5a4b50aa280d5d21e9b96dc674505bce2b35820c33e04926f89a9335c3f1ea658e4321d6480efb7ce75eb2729fbeeb07af40d426cd7be + languageName: node + linkType: hard + "@backstage/frontend-test-utils@npm:0.6.1": version: 0.6.1 resolution: "@backstage/frontend-test-utils@npm:0.6.1" @@ -3692,6 +3851,27 @@ __metadata: languageName: node linkType: hard +"@backstage/integration-react@npm:^1.2.20": + version: 1.2.20 + resolution: "@backstage/integration-react@npm:1.2.20" + dependencies: + "@backstage/config": "npm:^1.3.8" + "@backstage/core-plugin-api": "npm:^1.12.8" + "@backstage/integration": "npm:^2.0.3" + "@material-ui/core": "npm:^4.12.2" + "@material-ui/icons": "npm:^4.9.1" + 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/fe94dae7691c206ba325207fce9849d13142783583af62a661bba2718aff3dc4c5781837fce31cbf948bfd255c452c14886b76aa80af5a8bea32f7e9ee985991 + languageName: node + linkType: hard + "@backstage/integration@npm:^2.0.0, @backstage/integration@npm:^2.0.3": version: 2.0.3 resolution: "@backstage/integration@npm:2.0.3" @@ -3826,6 +4006,25 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-app-react@npm:^0.2.5": + version: 0.2.5 + resolution: "@backstage/plugin-app-react@npm:0.2.5" + dependencies: + "@backstage/core-plugin-api": "npm:^1.12.8" + "@backstage/frontend-plugin-api": "npm:^0.17.3" + "@material-ui/core": "npm:^4.9.13" + 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/9f570ec91eb2799e60193e9734a7d5e7981fb3314b7e297cb60df9f2734cfdc8e0c42296a7feef184c23ea4a38e9826eeba4cd6828dfba6bf08d2031f39853e9 + languageName: node + linkType: hard + "@backstage/plugin-app-visualizer@npm:0.2.5": version: 0.2.5 resolution: "@backstage/plugin-app-visualizer@npm:0.2.5" @@ -4447,6 +4646,52 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-catalog-react@npm:^3.2.0": + version: 3.2.0 + resolution: "@backstage/plugin-catalog-react@npm:3.2.0" + dependencies: + "@backstage/catalog-client": "npm:^1.16.1" + "@backstage/catalog-model": "npm:^1.9.0" + "@backstage/core-compat-api": "npm:^0.5.13" + "@backstage/core-components": "npm:^0.18.12" + "@backstage/core-plugin-api": "npm:^1.12.8" + "@backstage/errors": "npm:^1.3.1" + "@backstage/filter-predicates": "npm:^0.1.4" + "@backstage/frontend-plugin-api": "npm:^0.17.3" + "@backstage/integration-react": "npm:^1.2.20" + "@backstage/plugin-permission-common": "npm:^0.9.9" + "@backstage/plugin-permission-react": "npm:^0.5.3" + "@backstage/types": "npm:^1.2.2" + "@backstage/ui": "npm:^0.17.0" + "@backstage/version-bridge": "npm:^1.0.12" + "@material-ui/core": "npm:^4.12.2" + "@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" + classnames: "npm:^2.2.6" + lodash: "npm:^4.17.21" + material-ui-popup-state: "npm:^5.3.6" + qs: "npm:^6.15.2" + react-use: "npm:^17.2.4" + yaml: "npm:^2.0.0" + zen-observable: "npm:^0.10.0" + zod: "npm:^4.0.0" + peerDependencies: + "@backstage/frontend-test-utils": ^0.6.2 + "@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: + "@backstage/frontend-test-utils": + optional: true + "@types/react": + optional: true + checksum: 10c0/dee0b80998d0ab98c073e1dd688c6c4398e59f5a247402096a3d9b02c71e0f332f223203c530518ebc9e15347954c963f8e869ab03b7c37a4fda4e2ddbfc4244 + languageName: node + linkType: hard + "@backstage/plugin-catalog-unprocessed-entities-common@npm:^0.0.16": version: 0.0.16 resolution: "@backstage/plugin-catalog-unprocessed-entities-common@npm:0.0.16" @@ -4571,6 +4816,69 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-home-react@npm:^0.1.39": + version: 0.1.40 + resolution: "@backstage/plugin-home-react@npm:0.1.40" + dependencies: + "@backstage/core-compat-api": "npm:^0.5.13" + "@backstage/core-components": "npm:^0.18.12" + "@backstage/core-plugin-api": "npm:^1.12.8" + "@backstage/frontend-plugin-api": "npm:^0.17.3" + "@material-ui/core": "npm:^4.12.2" + "@material-ui/icons": "npm:^4.9.1" + "@rjsf/utils": "npm:5.24.13" + 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/7fb6272d2b267c02df951151a87f5dd04ac7aedb5f2e8d85ff6ae11762cb2305da6bda1d026a7772e0c3d561123041401c32cbb1eb6aa449dd5a93fd2d9b306e + languageName: node + linkType: hard + +"@backstage/plugin-home@npm:0.9.7": + version: 0.9.7 + resolution: "@backstage/plugin-home@npm:0.9.7" + dependencies: + "@backstage/catalog-client": "npm:^1.16.0" + "@backstage/catalog-model": "npm:^1.9.0" + "@backstage/config": "npm:^1.3.8" + "@backstage/core-app-api": "npm:^1.20.2" + "@backstage/core-compat-api": "npm:^0.5.12" + "@backstage/core-components": "npm:^0.18.11" + "@backstage/core-plugin-api": "npm:^1.12.7" + "@backstage/frontend-plugin-api": "npm:^0.17.2" + "@backstage/plugin-catalog-react": "npm:^3.1.0" + "@backstage/plugin-home-react": "npm:^0.1.39" + "@backstage/theme": "npm:^0.7.3" + "@material-ui/core": "npm:^4.12.2" + "@material-ui/icons": "npm:^4.9.1" + "@material-ui/lab": "npm:4.0.0-alpha.61" + "@rjsf/core": "npm:5.24.13" + "@rjsf/material-ui": "npm:5.24.13" + "@rjsf/utils": "npm:5.24.13" + "@rjsf/validator-ajv8": "npm:5.24.13" + lodash: "npm:^4.17.21" + luxon: "npm:^3.4.3" + react-grid-layout: "npm:1.3.4" + react-resizable: "npm:^3.0.4" + react-use: "npm:^17.2.4" + zod: "npm:^3.25.76 || ^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/5b765f0daec32ed7c9f7c2be26a251b226dd67cae46699bdbe3b92ce2521c7b8ee1ba0ffc4943f224f3ef8a1e75901423d48017026e14ca0abc46b4799ecc223 + languageName: node + linkType: hard + "@backstage/plugin-org@npm:0.7.5": version: 0.7.5 resolution: "@backstage/plugin-org@npm:0.7.5" @@ -4692,6 +5000,26 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-permission-react@npm:^0.5.3": + version: 0.5.3 + resolution: "@backstage/plugin-permission-react@npm:0.5.3" + dependencies: + "@backstage/config": "npm:^1.3.8" + "@backstage/core-plugin-api": "npm:^1.12.8" + "@backstage/plugin-permission-common": "npm:^0.9.9" + dataloader: "npm:^2.0.0" + swr: "npm:^2.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 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/2cc5c076fdc7bf28aee8dd68b8073b2f1439a9d8f73b8fb486517f94d84493e4ab36ac3bd4ca92b235d4f41a4849757f288c253488c45821d2c052ef815a32e4 + languageName: node + linkType: hard + "@backstage/plugin-proxy-backend@npm:0.6.14": version: 0.6.14 resolution: "@backstage/plugin-proxy-backend@npm:0.6.14" @@ -5293,6 +5621,33 @@ __metadata: languageName: node linkType: hard +"@backstage/ui@npm:^0.17.0": + version: 0.17.0 + resolution: "@backstage/ui@npm:0.17.0" + dependencies: + "@backstage/version-bridge": "npm:^1.0.12" + "@braintree/sanitize-url": "npm:^7.1.2" + "@internationalized/date": "npm:^3.12.0" + "@remixicon/react": "npm:>=4.6.0 <4.9.0" + "@tanstack/react-table": "npm:^8.21.3" + clsx: "npm:^2.1.1" + marked: "npm:^15.0.12" + react-aria: "npm:~3.48.0" + react-aria-components: "npm:~1.17.0" + react-stately: "npm:~3.46.0" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + "@types/react": ^18.0.0 + react: ^18.0.0 + react-dom: ^18.0.0 + react-router-dom: ^6.30.2 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/0e36fa81bfc6b872ef69e369e00fae6a020232e49e6997bb81ba027f783d6a8a539ecc761ed93cf5b80c2810ee71a20ef27e475f2a33b8343333c699348ad68d + languageName: node + linkType: hard + "@backstage/version-bridge@npm:^1.0.12": version: 1.0.12 resolution: "@backstage/version-bridge@npm:1.0.12" @@ -15962,6 +16317,7 @@ __metadata: "@backstage/plugin-catalog-import": "npm:0.13.14" "@backstage/plugin-catalog-react": "npm:3.1.0" "@backstage/plugin-catalog-unprocessed-entities": "npm:0.2.32" + "@backstage/plugin-home": "npm:0.9.7" "@backstage/plugin-permission-react": "npm:0.5.2" "@backstage/plugin-scaffolder": "npm:1.38.0" "@backstage/plugin-scaffolder-react": "npm:2.0.1" @@ -17792,7 +18148,7 @@ __metadata: languageName: node linkType: hard -"clsx@npm:^1.0.2, clsx@npm:^1.0.4, clsx@npm:^1.2.1": +"clsx@npm:^1.0.2, clsx@npm:^1.0.4, clsx@npm:^1.1.1, clsx@npm:^1.2.1": version: 1.2.1 resolution: "clsx@npm:1.2.1" checksum: 10c0/34dead8bee24f5e96f6e7937d711978380647e936a22e76380290e35486afd8634966ce300fc4b74a32f3762c7d4c0303f442c3e259f4ce02374eb0c82834f27 @@ -26301,6 +26657,13 @@ __metadata: languageName: node linkType: hard +"lodash.isequal@npm:^4.0.0": + version: 4.5.0 + resolution: "lodash.isequal@npm:4.5.0" + checksum: 10c0/dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f + languageName: node + linkType: hard + "lodash.isinteger@npm:^4.0.4": version: 4.0.4 resolution: "lodash.isinteger@npm:4.0.4" @@ -26546,7 +26909,7 @@ __metadata: languageName: node linkType: hard -"luxon@npm:3.7.2, luxon@npm:^3.0.0, luxon@npm:^3.2.1, luxon@npm:^3.5.0": +"luxon@npm:3.7.2, luxon@npm:^3.0.0, luxon@npm:^3.2.1, luxon@npm:^3.4.3, luxon@npm:^3.5.0": version: 3.7.2 resolution: "luxon@npm:3.7.2" checksum: 10c0/ed8f0f637826c08c343a29dd478b00628be93bba6f068417b1d8896b61cb61c6deacbe1df1e057dbd9298334044afa150f9aaabbeb3181418ac8520acfdc2ae2 @@ -31111,6 +31474,19 @@ __metadata: languageName: node linkType: hard +"react-draggable@npm:^4.0.0": + version: 4.7.1 + resolution: "react-draggable@npm:4.7.1" + dependencies: + clsx: "npm:^2.1.1" + prop-types: "npm:^15.8.1" + peerDependencies: + react: ">= 16.3.0" + react-dom: ">= 16.3.0" + checksum: 10c0/1ab282a51794bb62b09b2b781805adfc9b86f2b92266b23b28de03a45c54d38d99d522395e0b592430451def488c54313880a036367818344bd087592f2a9a32 + languageName: node + linkType: hard + "react-draggable@npm:^4.5.0": version: 4.7.0 resolution: "react-draggable@npm:4.7.0" @@ -31171,6 +31547,22 @@ __metadata: languageName: node linkType: hard +"react-grid-layout@npm:1.3.4": + version: 1.3.4 + resolution: "react-grid-layout@npm:1.3.4" + dependencies: + clsx: "npm:^1.1.1" + lodash.isequal: "npm:^4.0.0" + prop-types: "npm:^15.8.1" + react-draggable: "npm:^4.0.0" + react-resizable: "npm:^3.0.4" + peerDependencies: + react: ">= 16.3.0" + react-dom: ">= 16.3.0" + checksum: 10c0/2c4a9ca1284cf6a618070aeccf8ffb8d2d91798452f7606395a4524bda27fad82ba9c818cb3e420d617fec8aed93c0caaae060c714d21a929c6f5c75727697b7 + languageName: node + linkType: hard + "react-helmet@npm:6.1.0": version: 6.1.0 resolution: "react-helmet@npm:6.1.0" @@ -31388,7 +31780,7 @@ __metadata: languageName: node linkType: hard -"react-resizable@npm:^3.0.5": +"react-resizable@npm:^3.0.4, react-resizable@npm:^3.0.5": version: 3.2.0 resolution: "react-resizable@npm:3.2.0" dependencies: From 9918c88f9a088a6069364f709d73e96f18c97747 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Mon, 10 Aug 2026 16:09:59 -0400 Subject: [PATCH 06/48] chore: remove statically installed app-defaults plugins Signed-off-by: Frank Kong rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- packages/app/package.json | 3 --- packages/app/src/App.tsx | 7 ------ yarn.lock | 47 --------------------------------------- 3 files changed, 57 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 10076d6f6d..ee66e88dc1 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -88,9 +88,6 @@ "@mui/icons-material": "5.18.0", "@mui/material": "5.18.0", "@octokit/rest": "19.0.13", - "@red-hat-developer-hub/backstage-plugin-app-auth": "0.1.0", - "@red-hat-developer-hub/backstage-plugin-app-integrations": "0.1.0", - "@red-hat-developer-hub/backstage-plugin-app-react": "0.1.0", "@red-hat-developer-hub/backstage-plugin-theme": "0.14.7", "history": "5.3.0", "react": "18.3.1", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index a288942ae2..e7ae56f949 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -7,9 +7,6 @@ import searchPlugin from '@backstage/plugin-search/alpha'; import userSettingsPlugin from '@backstage/plugin-user-settings/alpha'; import homePagePlugin from '@backstage/plugin-home/alpha'; -import { appAuthModule } from '@red-hat-developer-hub/backstage-plugin-app-auth/alpha'; -import { appIntegrationsModule } from '@red-hat-developer-hub/backstage-plugin-app-integrations/alpha'; -import { appDrawerModule } from '@red-hat-developer-hub/backstage-plugin-app-react/alpha'; import { rhdhThemeModule } from '@red-hat-developer-hub/backstage-plugin-theme/alpha'; import { rhdhApisModule } from './apis/apisModule'; @@ -27,10 +24,6 @@ const app = createApp({ searchPlugin, userSettingsPlugin, dynamicFrontendFeaturesLoader(), - // RHDH modules (from rhdh-plugins workspace) - appAuthModule, // config-driven sign-in page + OIDC/Keycloak/PingFederate/Auth0/SAML auth APIs - appIntegrationsModule, // SCM integrations + SCM auth APIs - appDrawerModule, // drawer infrastructure (lightspeed, quickstarts, etc.) // RHDH modules (local to app) navModule, // RHDH-branded sidebar (logo, menu ordering, drawer toggle) userSettingsGeneralModule, // build-metadata InfoCard on Settings / General diff --git a/yarn.lock b/yarn.lock index 23a809a518..6106f23425 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11664,50 +11664,6 @@ __metadata: languageName: node linkType: hard -"@red-hat-developer-hub/backstage-plugin-app-auth@npm:0.1.0": - version: 0.1.0 - resolution: "@red-hat-developer-hub/backstage-plugin-app-auth@npm:0.1.0" - dependencies: - "@backstage/core-app-api": "npm:^1.20.2" - "@backstage/core-components": "npm:^0.18.11" - "@backstage/core-plugin-api": "npm:^1.12.7" - "@backstage/frontend-plugin-api": "npm:^0.17.2" - "@backstage/plugin-app": "npm:^0.5.0" - "@mui/material": "npm:5.18.0" - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - checksum: 10c0/966641ff36d3c0ba5ecb885dde7933a7fbabaadf61c0b66360560013d12f993fd45622bc58ef72388615f7096bfba6974d4d0a41d5177255af3737ec817d8e68 - languageName: node - linkType: hard - -"@red-hat-developer-hub/backstage-plugin-app-integrations@npm:0.1.0": - version: 0.1.0 - resolution: "@red-hat-developer-hub/backstage-plugin-app-integrations@npm:0.1.0" - dependencies: - "@backstage/core-plugin-api": "npm:^1.12.7" - "@backstage/frontend-plugin-api": "npm:^0.17.2" - "@backstage/integration-react": "npm:^1.2.19" - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - checksum: 10c0/8853a2abe4bec66a5fe356917215863840bccc8241ab2148afd62f49a102940d8846a9db6ee9295b46d72b474b46051c3203c9e38fbdf1c84163e056c5b9d53e - languageName: node - linkType: hard - -"@red-hat-developer-hub/backstage-plugin-app-react@npm:0.1.0": - version: 0.1.0 - resolution: "@red-hat-developer-hub/backstage-plugin-app-react@npm:0.1.0" - dependencies: - "@backstage/core-plugin-api": "npm:^1.12.7" - "@backstage/frontend-plugin-api": "npm:^0.17.2" - "@backstage/plugin-app-react": "npm:^0.2.4" - "@backstage/version-bridge": "npm:^1.0.12" - "@mui/material": "npm:5.18.0" - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - checksum: 10c0/5f1448076a831ed77c6c2dc7b3bf600afe87a46f7b6b90a2411238ddd5949687d2a5e19bab4718aedcd80587d7478621f03a460514f15d1f4d37395ee8d797ae - languageName: node - linkType: hard - "@red-hat-developer-hub/backstage-plugin-theme@npm:0.14.7": version: 0.14.7 resolution: "@red-hat-developer-hub/backstage-plugin-theme@npm:0.14.7" @@ -16334,9 +16290,6 @@ __metadata: "@mui/icons-material": "npm:5.18.0" "@mui/material": "npm:5.18.0" "@octokit/rest": "npm:19.0.13" - "@red-hat-developer-hub/backstage-plugin-app-auth": "npm:0.1.0" - "@red-hat-developer-hub/backstage-plugin-app-integrations": "npm:0.1.0" - "@red-hat-developer-hub/backstage-plugin-app-react": "npm:0.1.0" "@red-hat-developer-hub/backstage-plugin-theme": "npm:0.14.7" "@testing-library/dom": "npm:9.3.4" "@testing-library/jest-dom": "npm:6.9.1" From 8465ba3b825fb10f90538f1e0c551320498487a5 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Wed, 12 Aug 2026 09:34:19 -0400 Subject: [PATCH 07/48] chore: remove ENABLE_STANDARD_MODULE_FEDERATION Signed-off-by: Frank Kong rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- package.json | 2 +- packages/backend/package.json | 2 +- packages/backend/src/index.ts | 25 +--------------------- packages/backend/src/schemaLocator.test.ts | 4 ++-- packages/backend/src/schemaLocator.ts | 19 +++++++++------- 5 files changed, 16 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index 81cfff18c6..a82312b43b 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "prepare": "husky install", "start": "NODE_OPTIONS=--no-node-snapshot turbo run start --filter=backend", "dev:legacy": "NODE_OPTIONS=--no-node-snapshot turbo run start --filter=backend --filter=app-legacy", - "dev": "NODE_OPTIONS=--no-node-snapshot ENABLE_STANDARD_MODULE_FEDERATION=true turbo run start --filter=backend --filter=app", + "dev": "NODE_OPTIONS=--no-node-snapshot turbo run start --filter=backend --filter=app", "build": "turbo run build", "build:dockerfile": "bash ./scripts/update-Dockerfile.sh", "tsc": "turbo run tsc", diff --git a/packages/backend/package.json b/packages/backend/package.json index ea841b9b57..22733a3cf7 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -8,7 +8,7 @@ "role": "backend" }, "scripts": { - "start": "ENABLE_STANDARD_MODULE_FEDERATION=true backstage-cli package start --require ./src/instrumentation.js", + "start": "backstage-cli package start --require ./src/instrumentation.js", "start:legacy": "APP_CONFIG_app_packageName=app-legacy backstage-cli package start --require ./src/instrumentation.js", "start:prometheus": "docker run --mount type=bind,source=./prometheus.yml,destination=/etc/prometheus/prometheus.yml --publish published=9090,target=9090,protocol=tcp prom/prometheus", "build": "backstage-cli package build", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index b668818b4b..8125fe781c 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -3,11 +3,9 @@ import { WinstonLogger } from '@backstage/backend-defaults/rootLogger'; import { CommonJSModuleLoader, dynamicPluginsFeatureLoader, - dynamicPluginsFrontendServiceRef, } from '@backstage/backend-dynamic-feature-service'; -import { createServiceFactory } from '@backstage/backend-plugin-api'; -import * as path from 'path'; +import * as path from 'node:path'; import { configureCorporateProxyAgent } from './corporate-proxy'; import { getDefaultServiceFactories } from './defaultServiceFactories'; @@ -82,27 +80,6 @@ backend.add( }), ); -if ( - (process.env.ENABLE_STANDARD_MODULE_FEDERATION || '').toLocaleLowerCase() !== - 'true' -) { - // When the `dynamicPlugins` entry exists in the configuration, the upstream dynamic plugins backend feature loader - // also loads the `dynamicPluginsFrontendServiceRef` service that installs an http router to serve - // standard Module Federation assets for every installed dynamic frontend plugin. - // For now in RHDH the old frontend application doesn't use standard module federation and, by default, - // exported RHDH dynamic frontend plugins don't contain standard module federation assets. - // That's why we disable (bu overriding it with a noop) this service unless stadard module federation use - // is explicitly requested. - backend.add( - createServiceFactory({ - service: dynamicPluginsFrontendServiceRef, - deps: {}, - factory: () => ({ - setResolverProvider() {}, - }), - }), - ); -} backend.add(healthCheckPlugin); diff --git a/packages/backend/src/schemaLocator.test.ts b/packages/backend/src/schemaLocator.test.ts index 895a7b71db..15d1929901 100644 --- a/packages/backend/src/schemaLocator.test.ts +++ b/packages/backend/src/schemaLocator.test.ts @@ -42,11 +42,11 @@ describe('schemaLocator', () => { ); }); - it('falls back to the legacy dist-scalprum/configSchema.json path for frontend (web) plugins', () => { + it('does not fall back to the legacy OFS/Scalprum dist-scalprum path for frontend (web) plugins', () => { jest.spyOn(fs, 'existsSync').mockReturnValue(false); expect(schemaLocator(createPluginPackage('frontend-plugin'))).toBe( - path.join('dist-scalprum', 'configSchema.json'), + path.join('dist', '.config-schema.json'), ); }); }); diff --git a/packages/backend/src/schemaLocator.ts b/packages/backend/src/schemaLocator.ts index 39183958a7..327dc95374 100644 --- a/packages/backend/src/schemaLocator.ts +++ b/packages/backend/src/schemaLocator.ts @@ -11,11 +11,15 @@ import type { ScannedPluginPackage } from '@backstage/backend-dynamic-feature-se * * RHIDP-15079 (partial): prefers the modern `dist/.config-schema.json` path * written by `@backstage/cli`'s `cli-module-build` bundler for both frontend - * and backend packages, falling back to the legacy per-platform path written - * by the old janus-idp/cli (Scalprum) toolchain. + * and backend packages, falling back to the legacy `dist/configSchema.json` + * path for backend (node) packages predating that bundler. * - * The legacy fallback should be removed entirely once the legacy OFS frontend - * is deleted in RHIDP-15078/RHIDP-15079. + * The legacy `dist-scalprum/configSchema.json` fallback for frontend (web) + * packages — the old janus-idp/cli (OFS/Scalprum) toolchain's output — was + * removed now that RHDH only ships the new frontend system (`packages/app`). + * A frontend dynamic plugin without the modern schema path simply has no + * config schema gathered for it (see `gatherDynamicPluginsSchemas`, which + * skips missing files gracefully). */ export function schemaLocator(pluginPackage: ScannedPluginPackage): string { const pluginLocation = url.fileURLToPath(pluginPackage.location); @@ -27,8 +31,7 @@ export function schemaLocator(pluginPackage: ScannedPluginPackage): string { const platform = PackageRoles.getRoleInfo( pluginPackage.manifest.backstage.role, ).platform; - return path.join( - platform === 'node' ? 'dist' : 'dist-scalprum', - 'configSchema.json', - ); + return platform === 'node' + ? path.join('dist', 'configSchema.json') + : modernSchemaPath; } From b1865ccaf5420f69e57d450340797aa8dbc184a2 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Sun, 16 Aug 2026 22:36:46 -0400 Subject: [PATCH 08/48] chore: remove hardcoded sidebar logo in favor of global header plugin Signed-off-by: Frank Kong rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- packages/app/package.json | 3 +- packages/app/src/index.tsx | 1 + packages/app/src/modules/nav/LogoFull.tsx | 80 ------------ packages/app/src/modules/nav/LogoIcon.tsx | 19 --- packages/app/src/modules/nav/Sidebar.tsx | 17 --- .../app/src/modules/nav/SidebarLogo.test.tsx | 122 ------------------ packages/app/src/modules/nav/SidebarLogo.tsx | 73 ----------- translations/backstage-fr.json | 6 +- 8 files changed, 6 insertions(+), 315 deletions(-) delete mode 100644 packages/app/src/modules/nav/LogoFull.tsx delete mode 100644 packages/app/src/modules/nav/LogoIcon.tsx delete mode 100644 packages/app/src/modules/nav/SidebarLogo.test.tsx delete mode 100644 packages/app/src/modules/nav/SidebarLogo.tsx diff --git a/packages/app/package.json b/packages/app/package.json index ee66e88dc1..98d2c5b667 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -85,11 +85,12 @@ "@material-ui/core": "4.12.4", "@material-ui/icons": "4.11.3", "@material-ui/lab": "4.0.0-alpha.61", - "@mui/icons-material": "5.18.0", + "@mui/icons-material": "9.3.1", "@mui/material": "5.18.0", "@octokit/rest": "19.0.13", "@red-hat-developer-hub/backstage-plugin-theme": "0.14.7", "history": "5.3.0", + "material-icons": "1.13.14", "react": "18.3.1", "react-dom": "18.3.1", "react-router": "6.30.4", diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index ece7196ce0..084c48feed 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -2,5 +2,6 @@ import '@backstage/cli/asset-types'; import ReactDOM from 'react-dom/client'; import app from './App'; import '@backstage/ui/css/styles.css'; +import 'material-icons/iconfont/outlined.css'; ReactDOM.createRoot(document.getElementById('root')!).render(app); diff --git a/packages/app/src/modules/nav/LogoFull.tsx b/packages/app/src/modules/nav/LogoFull.tsx deleted file mode 100644 index e4130ef482..0000000000 --- a/packages/app/src/modules/nav/LogoFull.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { useAppBarBackgroundScheme } from '../../hooks/useThemedConfig'; - -export const LogoFull = (props: React.ComponentProps<'svg'>) => { - const appBarBackgroundScheme = useAppBarBackgroundScheme(); - - return ( - - - - - - - - - ); -}; diff --git a/packages/app/src/modules/nav/LogoIcon.tsx b/packages/app/src/modules/nav/LogoIcon.tsx deleted file mode 100644 index 422f860088..0000000000 --- a/packages/app/src/modules/nav/LogoIcon.tsx +++ /dev/null @@ -1,19 +0,0 @@ -export const LogoIcon = (props: React.ComponentProps<'svg'>) => { - return ( - - - - - ); -}; diff --git a/packages/app/src/modules/nav/Sidebar.tsx b/packages/app/src/modules/nav/Sidebar.tsx index 58bcc26f14..ed1f61024e 100644 --- a/packages/app/src/modules/nav/Sidebar.tsx +++ b/packages/app/src/modules/nav/Sidebar.tsx @@ -9,23 +9,9 @@ import { import { NavContentBlueprint } from '@backstage/plugin-app-react'; import { SidebarSearchModal } from '@backstage/plugin-search'; import { UserSettingsSignInAvatar } from '@backstage/plugin-user-settings'; -import { useAppDrawer } from '@red-hat-developer-hub/backstage-plugin-app-react'; -import ChatIcon from '@mui/icons-material/Chat'; import MenuIcon from '@mui/icons-material/Menu'; import SearchIcon from '@mui/icons-material/Search'; -import { SidebarLogo } from './SidebarLogo'; - -const ChatDrawerItem = () => { - const { toggleDrawer } = useAppDrawer(); - return ( - } - text="Chat" - onClick={() => toggleDrawer('lightspeed')} - /> - ); -}; export const SidebarContent = NavContentBlueprint.make({ params: { @@ -38,7 +24,6 @@ export const SidebarContent = NavContentBlueprint.make({ return ( - } to="/search"> @@ -54,8 +39,6 @@ export const SidebarContent = NavContentBlueprint.make({ - - } diff --git a/packages/app/src/modules/nav/SidebarLogo.test.tsx b/packages/app/src/modules/nav/SidebarLogo.test.tsx deleted file mode 100644 index 90e3b42582..0000000000 --- a/packages/app/src/modules/nav/SidebarLogo.test.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { BrowserRouter } from 'react-router-dom'; - -import { useSidebarOpenState } from '@backstage/core-components'; -import { useApi } from '@backstage/core-plugin-api'; - -import { render } from '@testing-library/react'; - -import { useAppBarThemedConfig } from '../../hooks/useThemedConfig'; -import { useTranslation } from '../../hooks/useTranslation'; -import { SidebarLogo } from './SidebarLogo'; - -jest.mock('@backstage/core-components', () => ({ - ...jest.requireActual('@backstage/core-components'), - useSidebarOpenState: jest.fn(), -})); - -jest.mock('@backstage/core-plugin-api', () => ({ - ...jest.requireActual('@backstage/core-plugin-api'), - useApi: jest.fn(), -})); - -jest.mock('../../hooks/useThemedConfig', () => ({ - ...jest.requireActual('../../hooks/useThemedConfig'), - useAppBarThemedConfig: jest.fn(), -})); - -jest.mock('../../hooks/useTranslation', () => ({ - useTranslation: jest.fn(), -})); - -describe('SidebarLogo', () => { - beforeEach(() => { - // Mock translation function for all tests - (useTranslation as any).mockReturnValue({ - t: jest.fn((key: string) => { - const translations: Record = { - 'sidebar.home': 'Home', - 'sidebar.homeLogo': 'Home logo', - }; - return translations[key] || key; - }), - }); - }); - - it('when sidebar is open renders the component with full logo base64 provided by config', () => { - (useApi as any).mockReturnValue({ - getOptional: jest.fn().mockReturnValue('fullLogoWidth'), - }); - (useAppBarThemedConfig as any).mockReturnValue('fullLogoBase64URI'); - - (useSidebarOpenState as any).mockReturnValue({ isOpen: true }); - const { getByTestId } = render( - - - , - ); - - const fullLogo = getByTestId('home-logo'); - expect(fullLogo).toBeInTheDocument(); - expect(fullLogo).toHaveAttribute('src', 'fullLogoBase64URI'); - expect(fullLogo).toHaveAttribute('alt', 'Home logo'); - - const logoLink = fullLogo.closest('a'); - expect(logoLink).toHaveAttribute('aria-label', 'Home'); - }); - - it('when sidebar is open renders the component with default full logo if config is undefined', () => { - (useApi as any).mockReturnValue({ - getOptional: jest.fn().mockReturnValue(undefined), - }); - - (useAppBarThemedConfig as any).mockReturnValue(undefined); - - (useSidebarOpenState as any).mockReturnValue({ isOpen: true }); - const { getByTestId } = render( - - - , - ); - - expect(getByTestId('app-full-logo')).toBeInTheDocument(); - }); - - it('when sidebar is closed renders the component with icon logo base64 provided by config', () => { - (useApi as any).mockReturnValue({ - getOptional: jest.fn().mockReturnValue('fullLogoWidth'), - }); - (useAppBarThemedConfig as any).mockReturnValue('iconLogoBase64URI'); - - (useSidebarOpenState as any).mockReturnValue({ isOpen: false }); - const { getByTestId } = render( - - - , - ); - - const fullLogo = getByTestId('home-logo'); - expect(fullLogo).toBeInTheDocument(); - expect(fullLogo).toHaveAttribute('src', 'iconLogoBase64URI'); - expect(fullLogo).toHaveAttribute('alt', 'Home logo'); - - const logoLink = fullLogo.closest('a'); - expect(logoLink).toHaveAttribute('aria-label', 'Home'); - }); - - it('when sidebar is closed renders the component with icon logo from default if not provided with config', () => { - (useApi as any).mockReturnValue({ - getOptional: jest.fn().mockReturnValue(undefined), - }); - - (useAppBarThemedConfig as any).mockReturnValue(undefined); - - (useSidebarOpenState as any).mockReturnValue({ isOpen: false }); - const { getByTestId } = render( - - - , - ); - - expect(getByTestId('app-icon-logo')).toBeInTheDocument(); - }); -}); diff --git a/packages/app/src/modules/nav/SidebarLogo.tsx b/packages/app/src/modules/nav/SidebarLogo.tsx deleted file mode 100644 index 899ca3244c..0000000000 --- a/packages/app/src/modules/nav/SidebarLogo.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import type { ComponentType } from 'react'; - -import { Link, useSidebarOpenState } from '@backstage/core-components'; -import { configApiRef, useApi } from '@backstage/core-plugin-api'; - -import { makeStyles } from 'tss-react/mui'; - -import { useAppBarThemedConfig } from '../../hooks/useThemedConfig'; -import { useTranslation } from '../../hooks/useTranslation'; -import { LogoFull } from './LogoFull'; -import { LogoIcon } from './LogoIcon'; - -const useStyles = makeStyles()({ - sidebarLogo: { - margin: '24px 0px 6px 24px', - }, -}); - -const LogoRender = ({ - base64Logo, - DefaultLogo, - width, - altText, -}: { - base64Logo: string | undefined; - DefaultLogo: ComponentType>; - width: string | number; - altText: string; -}) => { - return base64Logo ? ( - {altText} - ) : ( - - ); -}; - -export const SidebarLogo = () => { - const { classes } = useStyles(); - const { isOpen } = useSidebarOpenState(); - - const { t } = useTranslation(); - const configApi = useApi(configApiRef); - - const logoFullBase64URI = useAppBarThemedConfig('app.branding.fullLogo'); - - const fullLogoWidth = configApi - .getOptional('app.branding.fullLogoWidth') - ?.toString(); - - const logoIconBase64URI = useAppBarThemedConfig('app.branding.iconLogo'); - - return ( -
- - {isOpen ? ( - - ) : ( - - )} - -
- ); -}; diff --git a/translations/backstage-fr.json b/translations/backstage-fr.json index 9a2ad76b9b..aaebc56f9c 100644 --- a/translations/backstage-fr.json +++ b/translations/backstage-fr.json @@ -683,11 +683,11 @@ "themeToggle.names.auto": "Auto", "signOutMenu.title": "Se déconnecter", "signOutMenu.moreIconTitle": "davantage", - "pinToggle.title": "Pin Sidebar", + "pinToggle.title": "Épingler la barre latérale", "pinToggle.description": "Empêcher la fermeture de la barre latérale", "pinToggle.switchTitles.unpin": "Détacher la barre latérale", - "pinToggle.switchTitles.pin": "Pin Sidebar", - "pinToggle.ariaLabelTitle": "Pin Sidebar Switch", + "pinToggle.switchTitles.pin": "Épingler la barre latérale", + "pinToggle.ariaLabelTitle": "Commutateur de barre latérale à broches", "identityCard.title": "Identité en coulisses", "identityCard.noIdentityTitle": "Aucune identité en coulisses", "identityCard.userEntity": "Entité utilisateur", From b307595d19eae6868b9b070fe03e931d00312a00 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Mon, 17 Aug 2026 00:26:46 -0400 Subject: [PATCH 09/48] chore: revert accidental @mui/icons-material upgrade Signed-off-by: Frank Kong rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- packages/app/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/package.json b/packages/app/package.json index 98d2c5b667..1b49714b88 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -85,7 +85,7 @@ "@material-ui/core": "4.12.4", "@material-ui/icons": "4.11.3", "@material-ui/lab": "4.0.0-alpha.61", - "@mui/icons-material": "9.3.1", + "@mui/icons-material": "5.18.0", "@mui/material": "5.18.0", "@octokit/rest": "19.0.13", "@red-hat-developer-hub/backstage-plugin-theme": "0.14.7", From 51f80fe6df9cc94f40d86cff1d90d6eed38cddb2 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Mon, 17 Aug 2026 00:27:57 -0400 Subject: [PATCH 10/48] chore: update yarn.lock Signed-off-by: Frank Kong rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- yarn.lock | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/yarn.lock b/yarn.lock index 6106f23425..2ea43026d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16301,6 +16301,7 @@ __metadata: "@types/zen-observable": "npm:0.8.7" cross-env: "npm:7.0.3" history: "npm:5.3.0" + material-icons: "npm:1.13.14" react: "npm:18.3.1" react-dom: "npm:18.3.1" react-router: "npm:6.30.4" @@ -27037,6 +27038,13 @@ __metadata: languageName: node linkType: hard +"material-icons@npm:1.13.14": + version: 1.13.14 + resolution: "material-icons@npm:1.13.14" + checksum: 10c0/4c0f9a8ff02545ce7747c621b5ffa1738212628189c9419f4cd2e75f7f8cf9e98ed5f946724a2d7e169cfebc439875df0ba9f79ba94a574e16cbb0c5ce26e55a + languageName: node + linkType: hard + "material-ui-popup-state@npm:^5.3.6": version: 5.3.7 resolution: "material-ui-popup-state@npm:5.3.7" From dbaa774c8696f6e7d9df776510c260372fabc0b4 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Mon, 17 Aug 2026 01:06:51 -0400 Subject: [PATCH 11/48] chore: port learning paths to NFS Assisted-By: Cursor Signed-off-by: Frank Kong rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- packages/app/public/learning-paths/data.json | 321 ++++++++++++++++++ packages/app/src/App.tsx | 2 + .../learning-paths/LearningPathsPage.test.tsx | 114 +++++++ .../learning-paths/LearningPathsPage.tsx | 121 +++++++ .../app/src/modules/learning-paths/index.tsx | 31 ++ .../learning-paths/useLearningPathData.ts | 51 +++ packages/app/src/modules/nav/Sidebar.tsx | 1 + 7 files changed, 641 insertions(+) create mode 100644 packages/app/public/learning-paths/data.json create mode 100644 packages/app/src/modules/learning-paths/LearningPathsPage.test.tsx create mode 100644 packages/app/src/modules/learning-paths/LearningPathsPage.tsx create mode 100644 packages/app/src/modules/learning-paths/index.tsx create mode 100644 packages/app/src/modules/learning-paths/useLearningPathData.ts diff --git a/packages/app/public/learning-paths/data.json b/packages/app/public/learning-paths/data.json new file mode 100644 index 0000000000..299caa9dcd --- /dev/null +++ b/packages/app/public/learning-paths/data.json @@ -0,0 +1,321 @@ +[ + { + "paths": 6, + "minutes": 20, + "description": "Learn about k8s API fundamentals and the etcd operator, as well as how to use the Operator SDK with Go and Helm, review Ansible basics, and use Ansible to deploy Kubernetes modules.", + "label": "Building Operators on OpenShift", + "url": "https://developers.redhat.com/learn/openshift/operators" + }, + { + "paths": 3, + "minutes": 20, + "description": "For the best experience in this learning path, we suggest that you complete the following learning resources in the order shown. When you click on a resource, it will open in a new tab. Keep this page open so you can easily move on to the next resource!", + "label": "Configure a Jupyter notebook to use GPUs for AI/ML modeling", + "url": "https://developers.redhat.com/learn/openshift-data-science/configure-jupyter-notebook-use-gpus-aiml-modeling" + }, + { + "paths": 20, + "hours": 7, + "minutes": 40, + "description": "This hands on experience will introduce you to developing on OpenShift. Learn how to access OpenShift from the command line, deploy an existing Spring application, and scale up your application to handle increased web traffic.", + "label": "Deploy a Spring application on OpenShift", + "url": "https://developers.redhat.com/learn/openshift/deploy-spring-application-openshift" + }, + { + "paths": 8, + "hours": 1, + "description": "Follow these step-by-step instructions to get started with OpenShift API Management, and deploy and test your first API.", + "label": "Deploy and manage your first API", + "url": "https://developers.redhat.com/learn/deploy-and-manage-your-first-api" + }, + { + "paths": 5, + "hours": 1, + "description": "Continue your Apache Kafka journey by developing consumers and producers in Java to stream data using Red Hat OpenShift Streams for Apache Kafka.", + "label": "Develop consumers and producers in Java", + "url": "https://developers.redhat.com/learn/openshift-streams-for-apache-kafka/develop-consumers-and-producers-java" + }, + { + "paths": 2, + "hours": 1, + "minutes": 15, + "description": "Get started with Camel K by learning the basics and exploring how Camel K relates to API integration, serverless and event-driven applications, and Kafka Connectors.", + "label": "Develop with Camel K", + "url": "https://developers.redhat.com/learn/camel-k" + }, + { + "paths": 5, + "hours": 2, + "description": "Use GitOps and OpenShift Pipelines to implement Kubernetes-native CI/CD, automate and manage app deployment.\r\nDeveloped by Christian Hernandez, Natale Vinto, and Dewan Ahmed", + "label": "Develop with GitOps", + "url": "https://developers.redhat.com/learn/openshift/develop-gitops" + }, + { + "paths": 6, + "hours": 1, + "minutes": 15, + "description": "Learn how to access an OpenShift cluster, manage apps with the odo command-line tool, then try image and source-based deployment techniques.", + "label": "Developing on OpenShift", + "url": "https://developers.redhat.com/learn/openshift/develop-on-openshift" + }, + { + "paths": 10, + "hours": 2, + "minutes": 25, + "description": "Quarkus is a powerful technology that streamlines Java programming under OpenShift. In this learning path, you will learn:\r\n\r\n\u2022 How to create, test, and deploy Java code to OpenShift in a seamless development workflow. \r\n\u2022 How to take advantage of Live Coding to test and deploy your applications in real time as you code.\r\n\u2022 How to write fully functional, robust applications running in OpenShift without having to waste time writing a lot of redundant code.\r\n\u2022 How to use Quarkus to write data-driven applications using a variety of data sources with just a few lines of code.", + "label": "Developing OpenShift applications with Java and Quarkus", + "url": "https://developers.redhat.com/learn/openshift/developing-openshift-applications-java-and-quarkus" + }, + { + "paths": 2, + "minutes": 15, + "description": "Learn how to create, integrate, and automate applications on the fly with Middleware portfolios that can run on-site, in the cloud, or within a container platform, like OpenShift.", + "label": "Developing with Middleware", + "url": "https://developers.redhat.com/learn/middleware" + }, + { + "paths": 15, + "hours": 3, + "minutes": 50, + "description": "Learn how to implement a variety of the most common components in the PatternFly React component library.", + "label": "Developing with PatternFly React", + "url": "https://developers.redhat.com/learn/patternfly-react" + }, + { + "paths": 9, + "hours": 2, + "minutes": 10, + "description": "Learn Quarkus, a Kubernetes-native Java stack tailored for GraalVM and OpenJDK HotSpot. Stand up an application with a hello endpoint, then get familiar with Hibernate ORM via Panache, a Quarkus component.", + "label": "Developing with Quarkus", + "url": "https://developers.redhat.com/learn/quarkus" + }, + { + "paths": 7, + "hours": 1, + "minutes": 45, + "description": "Learn Spring, a popular Java framework used to organize third-party libraries, bootstrap embeddable runtimes, and build microservices applications.", + "label": "Developing with Spring", + "url": "https://developers.redhat.com/learn/spring-boot" + }, + { + "paths": 9, + "hours": 2, + "description": "No one likes a bad communicator! How do you make all the information flow between apps, microservices and connected devices? Kafka and Knative have the answers.", + "label": "Event-driven architecture", + "url": "https://developers.redhat.com/devnation/event-driven-architecture" + }, + { + "paths": 2, + "minutes": 15, + "description": "Poke around the OpenShift web console or command-line interfaces at your own speed in a free-form sandbox environment.", + "label": "Exploring OpenShift", + "url": "https://developers.redhat.com/learn/openshift/explore-openshift" + }, + { + "paths": 13, + "hours": 3, + "minutes": 40, + "description": "Start with the foundations to get hands-on experience creating and deploying applications in Red Hat OpenShift in Red Hat's interactive learning environments. In this learning path, you will learn:\r\n\r\n\u2022 The basic concepts behind OpenShift\r\n\u2022 How to create and deploy applications directly into an OpenShift cluster from source as well as Linux containers\r\n\u2022 How to work with Live Coding to compile and deploy the changes immediately and automatically into OpenShift\r\n\u2022 How to create data-driven applications using a variety of OpenShift data technologies", + "label": "Foundations of OpenShift", + "url": "https://developers.redhat.com/learn/openshift/foundations-openshift" + }, + { + "paths": 19, + "hours": 4, + "minutes": 15, + "description": "Everyone likes a camp romance. Argo Cd and Tekton are a match made in Kubernetes heaven. See what makes them tick!", + "label": "From DevOps to GitOps", + "url": "https://developers.redhat.com/devnation/devops-gitops" + }, + { + "paths": 13, + "hours": 3, + "minutes": 10, + "description": "Learn to simplify your tasks by creating a centralized and automated process with Ansible.", + "label": "Get started with Ansible", + "url": "https://developers.redhat.com/learn/ansible" + }, + { + "paths": 11, + "hours": 2, + "description": "Learn the basics of Red Hat Enterprise Linux, and how to leverage it with tools like Podman, Buildah, and SQL.", + "label": "Get started with Red Hat Enterprise Linux", + "url": "https://developers.redhat.com/learn/rhel" + }, + { + "paths": 4, + "minutes": 45, + "description": "In this learning path, you will start your Jupyter notebook server and select preferences for S3 usage. You will also learn how to access and download the data you create as well as analyze it, using a variety of skills and tools.", + "label": "How to access, download, and analyze data for S3 usage", + "url": "https://developers.redhat.com/learn/openshift-data-science/how-access-s3-data-then-download-and-analyze-it" + }, + { + "paths": 3, + "minutes": 30, + "description": "Learn how to create and train PyTorch models using Red Hat OpenShift AI platform. This hands-on tutorial covers setting up PyTorch workbenches, developing machine learning models, and leveraging GPU acceleration for efficient training.", + "label": "How to create a PyTorch model", + "url": "https://developers.redhat.com/articles/2024/05/02/model-training-red-hat-openshift-ai#model_training" + }, + { + "paths": 3, + "minutes": 30, + "description": "In this learning path, you will set up options for your Jupyter notebook server, then explore the MNIST dataset to refine your data. Finally, you will learn how to implement frameworks, layers, and nodes to create your TensorFlow model.", + "label": "How to create a TensorFlow model", + "url": "https://developers.redhat.com/learn/openshift-data-science/how-create-tensorflow-model" + }, + { + "paths": 1, + "minutes": 20, + "description": "Learn how to deploy a model using S2I.", + "label": "How to deploy a model using S2I", + "url": "https://developers.redhat.com/learn/openshift-data-science/how-deploy-model-using-s2i" + }, + { + "paths": 13, + "hours": 3, + "minutes": 10, + "description": "Learn how to deploy an application on a cluster using Red Hat OpenShift Service on AWS.", + "label": "How to deploy an application using Red Hat OpenShift Service on AWS", + "url": "https://developers.redhat.com/learn/openshift/how-deploy-application-using-red-hat-openshift-service-aws" + }, + { + "paths": 3, + "minutes": 15, + "description": "Intel OpenVINO is an open source toolkit for optimizing and deploying AI inference. It allows you to perform several optimizations to speed up AI/ML jobs executed on Intel hardware, such as reducing floating point precision and combining several model layers into one. This hands-on experience will demonstrate OpenVINO with the Red Hat OpenShift Data Science managed cloud service.", + "label": "How to get started with Intel OpenVINO", + "url": "https://developers.redhat.com/learn/openshift-data-science/get-started-intel-openvino" + }, + { + "paths": 5, + "hours": 1, + "description": "In this learning path, you will learn how to set up data science projects. You will also learn how to consistently reproduce or execute Jupyter notebooks in the data science projects and serve the developed models in the form of a web service on top of Red Hat OpenShift.", + "label": "How to set up and reproduce data science experiments", + "url": "https://developers.redhat.com/learn/openshift-data-science/how-set-and-reproduce-data-science-experiments" + }, + { + "paths": 2, + "minutes": 30, + "description": "In this learning path, you will install OpenShift Data Science on Red Hat OpenShift Service on AWS (ROSA), a fully managed application platform for building and deploying applications.", + "label": "Install Red Hat OpenShift Data Science in Red Hat OpenShift Service on AWS", + "url": "https://developers.redhat.com/learn/openshift-data-science/install-red-hat-openshift-data-science-red-hat-openshift-service-aws" + }, + { + "paths": 7, + "hours": 1, + "minutes": 30, + "description": "Learn about the fundamentals of Apache Kafka. This tutorial covers basic concepts of Kafka and its components.", + "label": "Kafka 101", + "url": "https://developers.redhat.com/learn/apache-kafka/kafka-101" + }, + { + "paths": 20, + "hours": 11, + "minutes": 15, + "description": "Spend time getting to know your new friend - Kubernetes. Learn more about this open source container orchestration system and make notes on commands, tips, and tricks to bring it to life.", + "label": "Kubernetes and friends", + "url": "https://developers.redhat.com/devnation/kubernetes-and-friends" + }, + { + "paths": 3, + "minutes": 5, + "description": "Launch and explore OpenShift API Management and its components.", + "label": "Launch and explore Red Hat OpenShift API Management", + "url": "https://developers.redhat.com/learn/launch-and-explore-red-hat-openshift-api-management" + }, + { + "paths": 4, + "minutes": 45, + "description": "In this learning path, we use a pre-existing Jupyter Notebook project to start exploring data science. Project Jupyter offers the interactive JupyterHub and JupyterLab tools, which we'll introduce in this path.", + "label": "Launch Red Hat OpenShift Data Science", + "url": "https://developers.redhat.com/learn/openshift-data-science/launch-red-hat-openshift-data-science" + }, + { + "paths": 4, + "hours": 3, + "minutes": 10, + "description": "Learn how to get started on your machine learning journey with OpenShift. You'll learn more about technologies like Jupyter notebooks, JupyterHub, Prometheus, and more.", + "label": "Mastering AI / ML with OpenShift", + "url": "https://developers.redhat.com/learn/openshift/ai-ml" + }, + { + "paths": 2, + "minutes": 15, + "description": "The Red hat OpenShift Data Science platform offers a number of documents and learning materials explaining how to use Red Hat managed services as well as managed services and software offered by our partners. Learning materials include how-to resources, tutorials, and quick starts.", + "label": "Red Hat OpenShift Data Science documentation and resources", + "url": "https://developers.redhat.com/learn/openshift-data-science/red-hat-openshift-data-science-documentation-and-resources" + }, + { + "paths": 9, + "hours": 1, + "description": "Learn how to secure the APIs deployed on OpenShift API Management with OpenID Connect (OIDC) and Red Hat\u2019s single sign-on (SSO) technology.", + "label": "Securing APIs on Red Hat OpenShift API Management", + "url": "https://developers.redhat.com/learn/securing-apis-red-hat-openshift-api-management" + }, + { + "paths": 8, + "minutes": 30, + "description": "Set up your first developer portal, and learn the basics of developer portal customization and access controls.", + "label": "Setting up your first developer portal", + "url": "https://developers.redhat.com/learn/setting-up-your-first-developer-portal" + }, + { + "paths": 7, + "hours": 1, + "minutes": 55, + "description": "Get up to speed with the underlying structures that make up a container architecture. You'll strengthen your understanding of container building, tagging, organization, and orchestration, as well as get a high-level overview of OCI-compliant tools.", + "label": "Understanding subsystems", + "url": "https://developers.redhat.com/learn/subsystems" + }, + { + "paths": 2, + "minutes": 15, + "description": "For the best experience in this learning path, we suggest that you complete the following learning resources in the order shown. When you click on a resource, it will open in a new tab. Keep this page open so you can easily move on to the next resource!", + "label": "Using projects in Red Hat OpenShift Data Science", + "url": "https://developers.redhat.com/learn/openshift-data-science/using-projects-red-hat-openshift-data-science" + }, + { + "paths": 1, + "minutes": 30, + "level": "Beginner", + "type": "Ansible Learning path", + "description": "Learn more about the fundamental concepts of Ansible, an open source IT automation solution that automates provisioning, configuration management, application deployment, orchestration, and many other IT processes.", + "label": "Introduction to Ansible", + "url": "https://red.ht/aap-lp-ansible-basics" + }, + { + "paths": 1, + "hours": 1, + "level": "Beginner", + "type": "Ansible Learning path", + "description": "Do a deep-dive into the features of Ansible VS Code extension, the Red Hat recommended way of creating your Ansible projects, which brings syntax-highlighting, auto-completion, linting, and generative AI to your automation creation workflows.", + "label": "Getting started with the Ansible VS Code extension", + "url": "https://red.ht/aap-lp-vscode-essentials" + }, + { + "paths": 1, + "minutes": 30, + "level": "Beginner", + "type": "Ansible Learning path", + "description": "Learn more about YAML and how Ansible Automation Platform uses it for content. YAML offers a straightforward, human-readable way to write Ansible configurations, tasks, and playbooks.", + "label": "YAML Essentials for Ansible", + "url": "https://red.ht/aap-lp-yaml-essentials" + }, + { + "paths": 1, + "hours": 1, + "level": "Beginner", + "type": "Ansible Learning path", + "description": "Learn how to create Ansible playbooks, the blueprints for automation tasks executed across an inventory of nodes. Playbooks tell Ansible which tasks to perform on which devices.", + "label": "Getting started with Ansible playbooks", + "url": "https://red.ht/aap-lp-getting-started-playbooks" + }, + { + "paths": 1, + "hours": 1, + "level": "Intermediate", + "type": "Ansible Learning path", + "description": "Learn more about Ansible Content Collections. Content Collections provide a format for organizing and sharing your Ansible content, such as modules, Playbooks, and documentation.", + "label": "Getting started with Content Collections", + "url": "https://red.ht/aap-lp-getting-started-collections" + } +] diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index e7ae56f949..bf24c69d0b 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -10,6 +10,7 @@ import homePagePlugin from '@backstage/plugin-home/alpha'; import { rhdhThemeModule } from '@red-hat-developer-hub/backstage-plugin-theme/alpha'; import { rhdhApisModule } from './apis/apisModule'; +import { learningPathsModule } from './modules/learning-paths'; import { navModule } from './modules/nav'; import { userSettingsGeneralModule } from './modules/user-settings'; import { rhdhTranslationsModule } from './translations/translationsModule'; @@ -27,6 +28,7 @@ const app = createApp({ // RHDH modules (local to app) navModule, // RHDH-branded sidebar (logo, menu ordering, drawer toggle) userSettingsGeneralModule, // build-metadata InfoCard on Settings / General + learningPathsModule, // Learning Paths page (/learning-paths) rhdhApisModule, // storage, learning-path, catalog-graph APIs rhdhTranslationsModule, // RHDH + plugin translation overrides (de, es, fr, it, ja) rhdhThemeModule, // RHDH light/dark themes diff --git a/packages/app/src/modules/learning-paths/LearningPathsPage.test.tsx b/packages/app/src/modules/learning-paths/LearningPathsPage.test.tsx new file mode 100644 index 0000000000..e8b2fb9d99 --- /dev/null +++ b/packages/app/src/modules/learning-paths/LearningPathsPage.test.tsx @@ -0,0 +1,114 @@ +/* + * Layer 3 component test mirroring the behavior of the UI E2E spec + * e2e-tests/playwright/e2e/learning-path-page.spec.ts (net new — the E2E spec + * is intentionally left in place). + */ +import { + renderInTestApp, + TestApiProvider, +} from '@backstage/frontend-test-utils'; +import { searchApiRef } from '@backstage/plugin-search-react'; + +import { screen } from '@testing-library/react'; + +import { LearningPaths } from './LearningPathsPage'; +import { useLearningPathData } from './useLearningPathData'; + +jest.mock('./useLearningPathData'); + +jest.mock('../../hooks/useTranslation', () => ({ + useTranslation: () => ({ t: (key: string) => key }), +})); + +const mockUseLearningPathData = useLearningPathData as jest.Mock; + +const searchApiMock = { query: jest.fn().mockResolvedValue({ results: [] }) }; + +const renderPage = () => + renderInTestApp( + + + , + ); + +describe('LearningPaths', () => { + // clearAllMocks (not resetAllMocks) so the module-scoped searchApiMock keeps + // its resolved implementation across tests; only call history is cleared. + afterEach(() => jest.clearAllMocks()); + + it('shows a progress indicator while loading', async () => { + mockUseLearningPathData.mockReturnValue({ + data: undefined, + error: undefined, + isLoading: true, + }); + + await renderPage(); + + expect(screen.getByRole('progressbar')).toBeInTheDocument(); + }); + + it('renders a card with an external link for each learning path', async () => { + mockUseLearningPathData.mockReturnValue({ + data: [ + { + label: 'Building Operators on OpenShift', + description: 'Learn about k8s API fundamentals', + url: 'https://developers.redhat.com/learn/openshift/operators', + hours: 1, + minutes: 20, + paths: 6, + }, + ], + error: undefined, + isLoading: false, + }); + + await renderPage(); + + expect( + screen.getByText('Building Operators on OpenShift'), + ).toBeInTheDocument(); + expect( + screen.getByText('Learn about k8s API fundamentals'), + ).toBeInTheDocument(); + expect( + screen.getByText('1 hour 20 minutes | 6 learning paths'), + ).toBeInTheDocument(); + + const link = screen.getByRole('link', { + name: /Building Operators on OpenShift/, + }); + expect(link).toHaveAttribute( + 'href', + 'https://developers.redhat.com/learn/openshift/operators', + ); + expect(link).toHaveAttribute('target', '_blank'); + }); + + it('shows an error report when no learning paths are returned', async () => { + mockUseLearningPathData.mockReturnValue({ + data: undefined, + error: undefined, + isLoading: false, + }); + + await renderPage(); + + expect( + screen.getByText(/app\.learningPaths\.error\.title/), + ).toBeInTheDocument(); + }); + + it('shows the underlying error message when loading fails', async () => { + mockUseLearningPathData.mockReturnValue({ + data: undefined, + error: new Error('Boom'), + isLoading: false, + }); + + await renderPage(); + + expect(screen.getByText(/Error: Boom/)).toBeInTheDocument(); + }); +}); diff --git a/packages/app/src/modules/learning-paths/LearningPathsPage.tsx b/packages/app/src/modules/learning-paths/LearningPathsPage.tsx new file mode 100644 index 0000000000..004cb49b41 --- /dev/null +++ b/packages/app/src/modules/learning-paths/LearningPathsPage.tsx @@ -0,0 +1,121 @@ +import { + CodeSnippet, + Content, + Header, + InfoCard, + Page, + WarningPanel, +} from '@backstage/core-components'; +import { SearchContextProvider } from '@backstage/plugin-search-react'; + +import CircularProgress from '@mui/material/CircularProgress'; +import Grid from '@mui/material/Grid'; +import Link from '@mui/material/Link'; +import Typography from '@mui/material/Typography'; +import { makeStyles } from 'tss-react/mui'; + +import { LearningPathLink } from '../../apis/LearningPathApiClient'; +import { useTranslation } from '../../hooks/useTranslation'; +import { useLearningPathData } from './useLearningPathData'; + +const useStyles = makeStyles()({ + infoCard: { + height: '100%', + transition: 'all 0.25s linear', + textAlign: 'left', + '&:hover': { + boxShadow: '0px 0px 16px 0px rgba(0, 0, 0, 0.8)', + }, + '& svg': { + fontSize: '80px', + }, + }, +}); + +const ErrorReport = ({ + title, + errorText, +}: { + title: string; + errorText: string; +}) => ( + + + +); + +const learningPathLengthInfo = (path: LearningPathLink) => { + const hoursText = path.hours === 1 ? 'hour' : 'hours'; + const minutesText = path.minutes === 1 ? 'minute' : 'minutes'; + + const hours = path.hours ? `${path.hours} ${hoursText}` : ''; + const minutes = path.minutes ? `${path.minutes} ${minutesText}` : ''; + + return `${hours} ${minutes} | ${path.paths} learning paths`; +}; + +const LearningPathCards = () => { + const { classes } = useStyles(); + const { t } = useTranslation(); + + const { data, error, isLoading } = useLearningPathData(); + + if (isLoading) { + return ; + } + + if (error) { + return ( + + ); + } + + if (!data) { + return ( + + ); + } + + return ( + + {data.map(p => ( + + + + {p.description} + + + + ))} + + ); +}; + +export const LearningPaths = () => { + const { t } = useTranslation(); + + return ( + + +
+ + + + + + + + + + ); +}; diff --git a/packages/app/src/modules/learning-paths/index.tsx b/packages/app/src/modules/learning-paths/index.tsx new file mode 100644 index 0000000000..34024a87f3 --- /dev/null +++ b/packages/app/src/modules/learning-paths/index.tsx @@ -0,0 +1,31 @@ +import { + createFrontendModule, + createRouteRef, + PageBlueprint, +} from '@backstage/frontend-plugin-api'; +import SchoolIcon from '@mui/icons-material/School'; + +/** + * RHDH Learning Paths page at `/learning-paths`. + * + * This module is intentionally kept local to `app` rather than upstreamed, + * since Learning Paths is a custom RHDH feature (not upstream Backstage). + */ +const learningPathsRouteRef = createRouteRef(); + +const learningPathsPage = PageBlueprint.make({ + name: 'learning-paths', + params: { + path: '/learning-paths', + routeRef: learningPathsRouteRef, + title: 'Learning Paths', + icon: , + loader: () => + import('./LearningPathsPage').then(m => ), + }, +}); + +export const learningPathsModule = createFrontendModule({ + pluginId: 'app', + extensions: [learningPathsPage], +}); diff --git a/packages/app/src/modules/learning-paths/useLearningPathData.ts b/packages/app/src/modules/learning-paths/useLearningPathData.ts new file mode 100644 index 0000000000..683c8d2674 --- /dev/null +++ b/packages/app/src/modules/learning-paths/useLearningPathData.ts @@ -0,0 +1,51 @@ +import { useCallback, useEffect, useState } from 'react'; +import useAsync from 'react-use/lib/useAsync'; + +import { useApi } from '@backstage/core-plugin-api'; + +import { + learningPathApiRef, + LearningPathLink, +} from '../../apis/LearningPathApiClient'; + +export const useLearningPathData = (): { + data: LearningPathLink[] | undefined; + error: Error | undefined; + isLoading: boolean; +} => { + const [isLoading, setIsLoading] = useState(true); + const [data, setData] = useState(); + const [error, setError] = useState(); + const client = useApi(learningPathApiRef); + const { + value, + error: apiError, + loading, + } = useAsync(() => { + return client.getLearningPathData(); + }); + + const fetchData = useCallback(async () => { + const res = await fetch('/learning-paths/data.json'); + const qsData = await res.json(); + setData(qsData); + setError(undefined); + setIsLoading(false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (apiError) { + setError(apiError); + fetchData().catch(err => { + setError(err); + setIsLoading(false); + }); + } else if (!loading && value) { + setData(value); + setIsLoading(false); + } + }, [apiError, fetchData, loading, setData, value]); + + return { data, error, isLoading }; +}; diff --git a/packages/app/src/modules/nav/Sidebar.tsx b/packages/app/src/modules/nav/Sidebar.tsx index ed1f61024e..4740c5f84c 100644 --- a/packages/app/src/modules/nav/Sidebar.tsx +++ b/packages/app/src/modules/nav/Sidebar.tsx @@ -31,6 +31,7 @@ export const SidebarContent = NavContentBlueprint.make({ }> {nav.take('page:home')} {nav.take('page:catalog')} + {nav.take('page:app/learning-paths')} {nav.take('page:scaffolder')} From 252141416bfe26a5f5513b73e266da867937a333 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Mon, 17 Aug 2026 01:47:46 -0400 Subject: [PATCH 12/48] chore: update e2e tests Assisted-By: Cursor Signed-off-by: Frank Kong rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED --- .../config_map/dynamic-plugins-config.yaml | 431 +++++++++-------- .github/workflows/e2e-cluster-free.yaml | 16 +- .gitignore | 2 + app-config.local-e2e.yaml | 440 +++++++++--------- ...migrating-config-to-new-frontend-system.md | 18 +- docs/e2e-tests/local-e2e-harness.md | 234 +++++++--- e2e-tests/local-harness/dynamic-plugins.yaml | 212 +++------ e2e-tests/local-harness/populate.sh | 63 ++- e2e-tests/oxlint.config.ts | 5 +- e2e-tests/package.json | 2 +- ...l.config.ts => playwright.local.config.ts} | 30 +- .../playwright/e2e/github-happy-path.spec.ts | 5 +- .../e2e/guest-signin-happy-path.spec.ts | 17 +- .../e2e/home-page-customization.spec.ts | 20 +- .../e2e/instance-health-check.spec.ts | 2 +- .../playwright/e2e/learning-path-page.spec.ts | 7 +- .../e2e/plugins/application-listener.spec.ts | 2 +- .../e2e/plugins/application-provider.spec.ts | 2 +- .../e2e/plugins/frontend/sidebar.spec.ts | 57 ++- .../scaffolder-relation-processor.spec.ts | 3 +- .../e2e/plugins/tmp-infocard-check.spec.ts | 16 - .../plugins/user-settings-info-card.spec.ts | 8 +- e2e-tests/playwright/e2e/settings.spec.ts | 6 +- e2e-tests/playwright/e2e/smoke-test.spec.ts | 2 +- .../playwright/e2e/verify-redis-cache.spec.ts | 2 +- .../support/local-harness-global-setup.ts | 6 +- .../support/pages/catalog-browse-page.ts | 17 +- .../playwright/support/pages/home-page.ts | 5 + .../playwright/support/pages/settings-page.ts | 14 +- .../playwright/support/pages/sidebar-page.ts | 47 +- .../playwright/support/pages/techdocs-page.ts | 4 +- .../support/selectors/page-selectors.ts | 13 + 32 files changed, 901 insertions(+), 807 deletions(-) rename e2e-tests/{playwright.legacy-local.config.ts => playwright.local.config.ts} (80%) delete mode 100644 e2e-tests/playwright/e2e/plugins/tmp-infocard-check.spec.ts diff --git a/.ci/pipelines/resources/config_map/dynamic-plugins-config.yaml b/.ci/pipelines/resources/config_map/dynamic-plugins-config.yaml index 72e9cffc5e..5f0f08f408 100644 --- a/.ci/pipelines/resources/config_map/dynamic-plugins-config.yaml +++ b/.ci/pipelines/resources/config_map/dynamic-plugins-config.yaml @@ -1,208 +1,227 @@ +# RHDH serves the Backstage new frontend system (packages/app) by default, which reads +# `app.extensions` below. See docs/dynamic-plugins/migrating-config-to-new-frontend-system.md +# for the full OFS -> NFS config mapping and known RHDH-specific gaps. +app: + extensions: + # Home page (red-hat-developer-hub.backstage-plugin-dynamic-home-page NFS export). + # `page:home` is disabled by default upstream (@backstage/plugin-home) and must be + # explicitly enabled — see the plugin's README ("New Frontend System" section) — + # otherwise "/" 404s even though the plugin's widget/layout extensions load fine via + # Module Federation. NFS home-page cards are a fixed set of built-in widgets + # (home-page-widget:home/*) auto-enabled by the plugin — arbitrary cards + # (Placeholder/Markdown/JokeCard/Headline) that the old OFS mountPoints used to add + # have no NFS equivalent, so home-page-customization.spec.ts only asserts the + # widgets that still exist: Quick Access, Featured Docs, Recently Visited, Top + # Visited. Search and Starred Catalog Entities are also still-existing widgets; + # they're asserted by guest-signin-happy-path.spec.ts instead. + - page:home: + config: + path: / + - api:home/visits: true + - app-root-element:home/visit-listener: true + # Global header: wrapper + translations attach to the core `app` plugin and must be + # explicitly enabled. The plugin's own NFS extensions (gh-component:global-header/*, + # gh-menu-item:global-header/*) are enabled by default — no pluginConfig needed. + - app-root-wrapper:app/global-header + - translation:app/global-header-translations + # NFS AppLanguageApi reads availableLanguages here — not from i18n.locales in + # app-config-rhdh.yaml. Without this, UserSettingsLanguageToggle hides itself + # (languages.length <= 1). + - api:app/app-language: + config: + availableLanguages: [en, de, es, fr, it, ja] + defaultLanguage: en + # customizable: false so `priority` actually governs ordering (with customizable: + # true, the layout extension keeps the plugin-registration order and ignores + # priority entirely). Keys must match each widget's `params.name` (not its + # blueprint `name`) — see + # rhdh-plugins/workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageCards.tsx. + # Breakpoints mirror app-config.local.yaml so the default NFS widgets render in a + # stable layout for home-page-customization.spec.ts and guest-signin-happy-path.spec.ts. + - home-page-layout:home/dynamic-homepage-layout: + config: + customizable: false + widgetLayout: + "Red Hat Developer Hub - Explore templates": + priority: 300 + breakpoints: + xl: + w: 12 + h: 5 + lg: + w: 12 + h: 5 + md: + w: 12 + h: 5 + sm: + w: 12 + h: 5 + xs: + w: 12 + h: 7.5 + xxs: + w: 12 + h: 13.5 + "Red Hat Developer Hub - Software Catalog": + priority: 200 + breakpoints: + xl: + w: 12 + h: 7 + lg: + w: 12 + h: 7 + md: + w: 12 + h: 8 + sm: + w: 12 + h: 9 + xs: + w: 12 + h: 11 + xxs: + w: 12 + h: 15 + "Red Hat Developer Hub - Onboarding": + priority: 100 + breakpoints: + xl: + w: 12 + h: 6 + lg: + w: 12 + h: 6 + md: + w: 12 + h: 7 + sm: + w: 12 + h: 8 + xs: + w: 12 + h: 9 + xxs: + w: 12 + h: 14 + "Quick Access Card": + priority: 90 + breakpoints: + xl: + w: 6 + h: 8 + x: 6 + lg: + w: 6 + h: 8 + x: 6 + md: + w: 6 + h: 8 + x: 6 + sm: + w: 12 + h: 8 + x: 6 + xs: + w: 12 + h: 8 + x: 6 + xxs: + w: 12 + h: 8 + x: 6 + "Featured docs": + priority: 80 + breakpoints: + xl: + w: 6 + h: 4 + lg: + w: 6 + h: 4 + md: + w: 6 + h: 4 + sm: + w: 12 + h: 4 + xs: + w: 12 + h: 4 + xxs: + w: 12 + h: 4 + "Catalog starred": + priority: 70 + breakpoints: + xl: + w: 6 + h: 4 + lg: + w: 6 + h: 4 + md: + w: 6 + h: 4 + sm: + w: 12 + h: 4 + xs: + w: 12 + h: 4 + xxs: + w: 12 + h: 4 + "Recently visited": + priority: 60 + breakpoints: + xl: + w: 6 + h: 4 + lg: + w: 6 + h: 4 + md: + w: 6 + h: 4 + sm: + w: 12 + h: 4 + xs: + w: 12 + h: 4 + xxs: + w: 12 + h: 4 + "Top visited": + priority: 50 + breakpoints: + xl: + w: 6 + h: 4 + lg: + w: 6 + h: 4 + md: + w: 6 + h: 4 + sm: + w: 12 + h: 4 + xs: + w: 12 + h: 4 + xxs: + w: 12 + h: 4 + # Nested sidebar menu groups (e.g. "References" > APIs/Learning Paths, "Favorites" > + # Docs) have no NFS equivalent: the app's sidebar + # (packages/app/src/modules/nav/Sidebar.tsx) is a fixed, code-defined + # NavContentBlueprint (Search / Menu / Settings groups, with Catalog and Create + # pinned and everything else flat, sorted by title) — it does not read menuItems + # config at all. See sidebar.spec.ts for the flat-nav assertions this drives + # instead. dynamicPlugins: rootDirectory: dynamic-plugins-root - frontend: - red-hat-developer-hub.backstage-plugin-dynamic-home-page: - mountPoints: - - mountPoint: application/listener - importName: VisitListener - - mountPoint: home.page/cards - importName: SearchBar - config: - layouts: - xl: { w: 10, h: 1, x: 1 } - lg: { w: 10, h: 1, x: 1 } - md: { w: 10, h: 1, x: 1 } - sm: { w: 10, h: 1, x: 1 } - xs: { w: 12, h: 1 } - xxs: { w: 12, h: 1 } - - mountPoint: home.page/cards - importName: QuickAccessCard - config: - layouts: - xl: { w: 7, h: 8 } - lg: { w: 7, h: 8 } - md: { w: 7, h: 8 } - sm: { w: 12, h: 8 } - xs: { w: 12, h: 8 } - xxs: { w: 12, h: 8 } - - mountPoint: home.page/cards - importName: CatalogStarredEntitiesCard - config: - layouts: - xl: { w: 5, h: 4, x: 7 } - lg: { w: 5, h: 4, x: 7 } - md: { w: 5, h: 4, x: 7 } - sm: { w: 12, h: 4 } - xs: { w: 12, h: 4 } - xxs: { w: 12, h: 4 } - - - mountPoint: home.page/cards - importName: Headline - config: - layouts: - xl: { w: 12, h: 1 } - lg: { w: 12, h: 1 } - md: { w: 12, h: 1 } - sm: { w: 12, h: 1 } - xs: { w: 12, h: 1 } - xxs: { w: 12, h: 1 } - props: - title: Placeholder tests - align: center - - - mountPoint: home.page/cards - importName: Placeholder - config: - layouts: - xl: { x: 1, y: 0, w: 10, h: 1 } - lg: { x: 1, y: 0, w: 10, h: 1 } - md: { x: 1, y: 0, w: 10, h: 1 } - sm: { x: 0, y: 0, w: 12, h: 1 } - xs: { x: 0, y: 0, w: 12, h: 1 } - xxs: { x: 0, y: 0, w: 12, h: 1 } - props: - showBorder: true - debugContent: Home page customization test 1 - - mountPoint: home.page/cards - importName: Placeholder - config: - layouts: - xl: { x: 0, y: 0, w: 7, h: 4 } - lg: { x: 0, y: 0, w: 7, h: 4 } - md: { x: 0, y: 0, w: 7, h: 4 } - sm: { x: 0, y: 0, w: 12, h: 4 } - xs: { x: 0, y: 0, w: 12, h: 4 } - xxs: { x: 0, y: 0, w: 12, h: 4 } - props: - showBorder: true - debugContent: Home page customization test 2 - - mountPoint: home.page/cards - importName: Placeholder - config: - layouts: - xl: { x: 7, y: 0, w: 5, h: 4 } - lg: { x: 7, y: 0, w: 5, h: 4 } - md: { x: 7, y: 0, w: 5, h: 4 } - sm: { x: 0, y: 0, w: 12, h: 4 } - xs: { x: 0, y: 0, w: 12, h: 4 } - xxs: { x: 0, y: 0, w: 12, h: 4 } - props: - showBorder: true - debugContent: Home page customization test 3 - - mountPoint: home.page/cards - importName: Headline - config: - layouts: - xl: { w: 12, h: 1 } - lg: { w: 12, h: 1 } - md: { w: 12, h: 1 } - sm: { w: 12, h: 1 } - xs: { w: 12, h: 1 } - xxs: { w: 12, h: 1 } - props: - title: Markdown tests - align: center - - - mountPoint: home.page/cards - importName: MarkdownCard - config: - layouts: - xl: { w: 6, h: 4 } - lg: { w: 6, h: 4 } - md: { w: 6, h: 4 } - sm: { w: 6, h: 4 } - xs: { w: 6, h: 4 } - xxs: { w: 6, h: 4 } - props: - title: Company links - content: | - ### RHDH - - * [Website](https://developers.redhat.com/rhdh/overview) - * [Documentation](https://docs.redhat.com/en/documentation/red_hat_developer_hub/) - * [GitHub Showcase](https://github.com/redhat-developer/rhdh) - * [GitHub Plugins](https://github.com/janus-idp/backstage-plugins) - - mountPoint: home.page/cards - importName: Markdown - config: - layouts: - xl: { w: 6, h: 4, x: 6 } - lg: { w: 6, h: 4, x: 6 } - md: { w: 6, h: 4, x: 6 } - sm: { w: 6, h: 4, x: 6 } - xs: { w: 6, h: 4, x: 6 } - xxs: { w: 6, h: 4, x: 6 } - props: - title: Important company links - content: | - ### RHDH - - * [Website](https://developers.redhat.com/rhdh/overview) - * [Documentation](https://docs.redhat.com/en/documentation/red_hat_developer_hub/) - * [GitHub Showcase](https://github.com/redhat-developer/rhdh) - * [GitHub Plugins](https://github.com/janus-idp/backstage-plugins) - - mountPoint: home.page/cards - importName: FeaturedDocsCard - - mountPoint: home.page/cards - importName: JokeCard - - mountPoint: home.page/cards - importName: RecentlyVisitedCard - config: - layouts: - xl: { w: 6, h: 4, x: 6 } - lg: { w: 6, h: 4, x: 6 } - md: { w: 6, h: 4, x: 6 } - sm: { w: 6, h: 4, x: 6 } - xs: { w: 6, h: 4, x: 6 } - xxs: { w: 6, h: 4, x: 6 } - - mountPoint: home.page/cards - importName: TopVisitedCard - config: - layouts: - xl: { w: 6, h: 4 } - lg: { w: 6, h: 4 } - md: { w: 6, h: 4 } - sm: { w: 6, h: 4 } - xs: { w: 6, h: 4 } - xxs: { w: 6, h: 4 } - - default.main-menu-items: - menuItems: - default.list: - title: References - icon: bookmarks - default.apis: - parent: default.list - default.learning-path: - parent: default.list - default.test_enabled: - title: Test enabled - enabled: true - default.test_nested_enabled: - parent: default.test_enabled - title: Test nested enabled - enabled: true - default.test_nested_disabled: - parent: default.test_enabled - title: Test nested disabled - enabled: false - default.test_disabled: - title: Test disabled - enabled: false - backstage.plugin-techdocs: - menuItems: - favorites: - title: Favorites - icon: star - priority: 10 - docs: - parent: favorites - priority: 1 - test_i_enabled: - title: Test_i enabled - priority: 20 - enabled: true - test_i_disabled: - title: Test_i disabled - priority: 20 - enabled: false diff --git a/.github/workflows/e2e-cluster-free.yaml b/.github/workflows/e2e-cluster-free.yaml index 7f12e06523..c1a38b6f65 100644 --- a/.github/workflows/e2e-cluster-free.yaml +++ b/.github/workflows/e2e-cluster-free.yaml @@ -1,8 +1,8 @@ name: E2E Cluster-free # Runs the cluster-free local E2E harness (RHIDP-13501 / RHIDP-15075): boots the -# backend and the legacy app dev server in-process and drives Playwright against -# them, with dynamic plugins installed from the public OCI registry (ghcr) via +# backend and the NFS app (packages/app) dev server in-process and drives Playwright +# against them, with dynamic plugins installed from the public OCI registry (ghcr) via # the install-dynamic-plugins CLI (skopeo). No OpenShift/Kubernetes cluster or # image. See docs/e2e-tests/local-e2e-harness.md. @@ -77,20 +77,18 @@ jobs: run: yarn install --mode=skip-build - name: Populate dynamic-plugins-root (OCI, no source build) - # install-dynamic-plugins pulls the harness plugin set from the public OCI - # registry (ghcr) via skopeo — the core plugins in the catalog index's - # default.yaml reference local ./dynamic-plugins/dist paths that only exist - # after a source build, so we install the OCI-published builds instead. + # populate.sh resolves {{inherit}} against the full repo-root + # dynamic-plugins.default.yaml then installs the harness plugin set from quay.io. run: ./e2e-tests/local-harness/populate.sh - name: Run cluster-free E2E (NFS app) working-directory: ./e2e-tests - run: yarn e2e:legacy-local + run: yarn e2e:local - name: Upload Playwright report if: ${{ !cancelled() }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: - name: playwright-report-legacy-local - path: e2e-tests/playwright-report-legacy-local + name: playwright-report-local + path: e2e-tests/playwright-report-local retention-days: 7 diff --git a/.gitignore b/.gitignore index 8345b9d48f..f0e18f0c06 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,8 @@ dynamic-plugins-root/* !dynamic-plugins-root/.gitkeep # install-dynamic-plugins config copied to the repo root by e2e-tests/local-harness/populate.sh /dynamic-plugins.yaml +# catalog-index DPDY extracted by e2e-tests/local-harness/populate.sh when CATALOG_INDEX_IMAGE is set +e2e-tests/local-harness/.generated/ #dev caches .webpack-cache diff --git a/app-config.local-e2e.yaml b/app-config.local-e2e.yaml index 07c63bdd64..36e7064c5d 100644 --- a/app-config.local-e2e.yaml +++ b/app-config.local-e2e.yaml @@ -3,7 +3,7 @@ # Layered on top of app-config.yaml and app-config.dynamic-plugins.yaml to run # Playwright E2E without an OpenShift/Kubernetes cluster or container images: # -# yarn --cwd e2e-tests e2e:legacy-local +# yarn --cwd e2e-tests e2e:local # # It enables guest sign-in (the auth backend rejects guest unless a provider is # configured) and pins the in-memory SQLite database so a single `run` is fully @@ -63,214 +63,230 @@ catalog: rules: - allow: [User, Group] -# The e2e specs are written against the CI deployment's customization -# (.ci/pipelines/resources/config_map/dynamic-plugins-config.yaml): APIs and -# Learning Paths nest under a "References" sidebar group, and the home page -# carries the extra test cards (Placeholder/Markdown/Featured Docs/Random -# Joke/Top + Recently Visited). Mirror those blocks here so the specs pass -# off-cluster. The home-page mountPoints array REPLACES the one from the -# static app-config.dynamic-plugins.yaml (arrays substitute on config merge), -# so it is the CI list verbatim, superset of the static one. -dynamicPlugins: - frontend: - default.main-menu-items: - menuItems: - default.list: - title: References - icon: bookmarks - default.apis: - parent: default.list - default.learning-path: - parent: default.list - default.test_enabled: - title: Test enabled - enabled: true - default.test_nested_enabled: - parent: default.test_enabled - title: Test nested enabled - enabled: true - default.test_nested_disabled: - parent: default.test_enabled - title: Test nested disabled - enabled: false - default.test_disabled: - title: Test disabled - enabled: false - backstage.plugin-techdocs: - menuItems: - favorites: - title: Favorites - icon: star - priority: 10 - docs: - parent: favorites - priority: 1 - test_i_enabled: - title: Test_i enabled - priority: 20 - enabled: true - test_i_disabled: - title: Test_i disabled - priority: 20 - enabled: false - red-hat-developer-hub.backstage-plugin-dynamic-home-page: - mountPoints: - - mountPoint: application/listener - importName: VisitListener - - mountPoint: home.page/cards - importName: SearchBar - config: - layouts: - xl: { w: 10, h: 1, x: 1 } - lg: { w: 10, h: 1, x: 1 } - md: { w: 10, h: 1, x: 1 } - sm: { w: 10, h: 1, x: 1 } - xs: { w: 12, h: 1 } - xxs: { w: 12, h: 1 } - - mountPoint: home.page/cards - importName: QuickAccessCard - config: - layouts: - xl: { w: 7, h: 8 } - lg: { w: 7, h: 8 } - md: { w: 7, h: 8 } - sm: { w: 12, h: 8 } - xs: { w: 12, h: 8 } - xxs: { w: 12, h: 8 } - - mountPoint: home.page/cards - importName: CatalogStarredEntitiesCard - config: - layouts: - xl: { w: 5, h: 4, x: 7 } - lg: { w: 5, h: 4, x: 7 } - md: { w: 5, h: 4, x: 7 } - sm: { w: 12, h: 4 } - xs: { w: 12, h: 4 } - xxs: { w: 12, h: 4 } - - mountPoint: home.page/cards - importName: Headline - config: - layouts: - xl: { w: 12, h: 1 } - lg: { w: 12, h: 1 } - md: { w: 12, h: 1 } - sm: { w: 12, h: 1 } - xs: { w: 12, h: 1 } - xxs: { w: 12, h: 1 } - props: - title: Placeholder tests - align: center - - mountPoint: home.page/cards - importName: Placeholder - config: - layouts: - xl: { x: 1, y: 0, w: 10, h: 1 } - lg: { x: 1, y: 0, w: 10, h: 1 } - md: { x: 1, y: 0, w: 10, h: 1 } - sm: { x: 0, y: 0, w: 12, h: 1 } - xs: { x: 0, y: 0, w: 12, h: 1 } - xxs: { x: 0, y: 0, w: 12, h: 1 } - props: - showBorder: true - debugContent: Home page customization test 1 - - mountPoint: home.page/cards - importName: Placeholder - config: - layouts: - xl: { x: 0, y: 0, w: 7, h: 4 } - lg: { x: 0, y: 0, w: 7, h: 4 } - md: { x: 0, y: 0, w: 7, h: 4 } - sm: { x: 0, y: 0, w: 12, h: 4 } - xs: { x: 0, y: 0, w: 12, h: 4 } - xxs: { x: 0, y: 0, w: 12, h: 4 } - props: - showBorder: true - debugContent: Home page customization test 2 - - mountPoint: home.page/cards - importName: Placeholder - config: - layouts: - xl: { x: 7, y: 0, w: 5, h: 4 } - lg: { x: 7, y: 0, w: 5, h: 4 } - md: { x: 7, y: 0, w: 5, h: 4 } - sm: { x: 0, y: 0, w: 12, h: 4 } - xs: { x: 0, y: 0, w: 12, h: 4 } - xxs: { x: 0, y: 0, w: 12, h: 4 } - props: - showBorder: true - debugContent: Home page customization test 3 - - mountPoint: home.page/cards - importName: Headline - config: - layouts: - xl: { w: 12, h: 1 } - lg: { w: 12, h: 1 } - md: { w: 12, h: 1 } - sm: { w: 12, h: 1 } - xs: { w: 12, h: 1 } - xxs: { w: 12, h: 1 } - props: - title: Markdown tests - align: center - - mountPoint: home.page/cards - importName: MarkdownCard - config: - layouts: - xl: { w: 6, h: 4 } - lg: { w: 6, h: 4 } - md: { w: 6, h: 4 } - sm: { w: 6, h: 4 } - xs: { w: 6, h: 4 } - xxs: { w: 6, h: 4 } - props: - title: Company links - content: | - ### RHDH - - * [Website](https://developers.redhat.com/rhdh/overview) - * [Documentation](https://docs.redhat.com/en/documentation/red_hat_developer_hub/) - * [GitHub Showcase](https://github.com/redhat-developer/rhdh) - * [GitHub Plugins](https://github.com/janus-idp/backstage-plugins) - - mountPoint: home.page/cards - importName: Markdown - config: - layouts: - xl: { w: 6, h: 4, x: 6 } - lg: { w: 6, h: 4, x: 6 } - md: { w: 6, h: 4, x: 6 } - sm: { w: 6, h: 4, x: 6 } - xs: { w: 6, h: 4, x: 6 } - xxs: { w: 6, h: 4, x: 6 } - props: - title: Important company links - content: | - ### RHDH - - * [Website](https://developers.redhat.com/rhdh/overview) - * [Documentation](https://docs.redhat.com/en/documentation/red_hat_developer_hub/) - * [GitHub Showcase](https://github.com/redhat-developer/rhdh) - * [GitHub Plugins](https://github.com/janus-idp/backstage-plugins) - - mountPoint: home.page/cards - importName: FeaturedDocsCard - - mountPoint: home.page/cards - importName: JokeCard - - mountPoint: home.page/cards - importName: RecentlyVisitedCard - config: - layouts: - xl: { w: 6, h: 4, x: 6 } - lg: { w: 6, h: 4, x: 6 } - md: { w: 6, h: 4, x: 6 } - sm: { w: 6, h: 4, x: 6 } - xs: { w: 6, h: 4, x: 6 } - xxs: { w: 6, h: 4, x: 6 } - - mountPoint: home.page/cards - importName: TopVisitedCard - config: - layouts: - xl: { w: 6, h: 4 } - lg: { w: 6, h: 4 } - md: { w: 6, h: 4 } - sm: { w: 6, h: 4 } - xs: { w: 6, h: 4 } - xxs: { w: 6, h: 4 } +# The app (NFS, default) reads `app.extensions` below, mirroring the CI ConfigMap +# (.ci/pipelines/resources/config_map/dynamic-plugins-config.yaml): the home page route +# plus a customized home-page widget layout. `page:home` is disabled by default upstream +# (@backstage/plugin-home) and must be explicitly enabled — see the +# dynamic-home-page plugin's README ("New Frontend System" section) — otherwise "/" 404s +# even though the plugin's widget/layout extensions load fine via Module Federation. NFS +# home-page cards are a fixed set of built-in widgets (home-page-widget:home/*) — +# arbitrary cards like the old OFS Placeholder/Markdown/JokeCard/Headline mount points +# have no NFS equivalent, and nested sidebar menu groups (References/Favorites) have no +# NFS equivalent either (the sidebar is a fixed NavContentBlueprint in +# packages/app/src/modules/nav/Sidebar.tsx, not menuItems-config-driven). See +# home-page-customization.spec.ts and sidebar.spec.ts for the assertions this drives — +# guest-signin-happy-path.spec.ts additionally covers the Search and Starred Catalog +# Entities widgets. +app: + extensions: + - page:home: + config: + path: / + - api:home/visits: true + # app-root-element:home/visit-listener (also enabled in the CI ConfigMap) is + # deliberately NOT enabled here. It calls VisitsStorageApi.save() — an unguarded + # backend call — on every route change, which 404s because userSettings.persistence: + # browser (above) means the backend never loads @backstage/plugin-user-settings-backend. + # In a production build that 404 is just a background error; under this harness's dev + # server (backstage-cli package start), the resulting unhandled rejection triggers + # webpack's react-refresh error overlay, which is a full-screen transparent iframe that + # then intercepts every click for the rest of the test. api:home/visits above is enough + # to keep the Top/Recently Visited home-page widgets rendering (just always empty here). + # The global-header plugin's own default extensions (gh-component:global-header/*, + # gh-menu-item:global-header/*) are enabled by default — see + # e2e-tests/local-harness/dynamic-plugins.yaml. Only the wrapper + translation + # module need explicit enabling here, since they attach to the core `app` + # plugin rather than being scoped to the global-header plugin itself. + - app-root-wrapper:app/global-header + - translation:app/global-header-translations + # NFS AppLanguageApi reads availableLanguages here — not from i18n.locales above. + # Without this, UserSettingsLanguageToggle hides itself (languages.length <= 1). + - api:app/app-language: + config: + availableLanguages: [en, de, es, fr, it, ja] + defaultLanguage: en + # customizable: false so `priority` actually governs ordering (with customizable: + # true, the layout extension keeps the plugin-registration order and ignores + # priority entirely). Keys must match each widget's `params.name` (not its blueprint + # `name`) — see + # rhdh-plugins/workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageCards.tsx. + # Breakpoints mirror app-config.local.yaml / the CI ConfigMap so default NFS widgets + # render in a stable layout for home-page-customization.spec.ts. + - home-page-layout:home/dynamic-homepage-layout: + config: + customizable: false + widgetLayout: + "Red Hat Developer Hub - Explore templates": + priority: 300 + breakpoints: + xl: + w: 12 + h: 5 + lg: + w: 12 + h: 5 + md: + w: 12 + h: 5 + sm: + w: 12 + h: 5 + xs: + w: 12 + h: 7.5 + xxs: + w: 12 + h: 13.5 + "Red Hat Developer Hub - Software Catalog": + priority: 200 + breakpoints: + xl: + w: 12 + h: 7 + lg: + w: 12 + h: 7 + md: + w: 12 + h: 8 + sm: + w: 12 + h: 9 + xs: + w: 12 + h: 11 + xxs: + w: 12 + h: 15 + "Red Hat Developer Hub - Onboarding": + priority: 100 + breakpoints: + xl: + w: 12 + h: 6 + lg: + w: 12 + h: 6 + md: + w: 12 + h: 7 + sm: + w: 12 + h: 8 + xs: + w: 12 + h: 9 + xxs: + w: 12 + h: 14 + "Quick Access Card": + priority: 90 + breakpoints: + xl: + w: 6 + h: 8 + x: 6 + lg: + w: 6 + h: 8 + x: 6 + md: + w: 6 + h: 8 + x: 6 + sm: + w: 12 + h: 8 + x: 6 + xs: + w: 12 + h: 8 + x: 6 + xxs: + w: 12 + h: 8 + x: 6 + "Featured docs": + priority: 80 + breakpoints: + xl: + w: 6 + h: 4 + lg: + w: 6 + h: 4 + md: + w: 6 + h: 4 + sm: + w: 12 + h: 4 + xs: + w: 12 + h: 4 + xxs: + w: 12 + h: 4 + "Catalog starred": + priority: 70 + breakpoints: + xl: + w: 6 + h: 4 + lg: + w: 6 + h: 4 + md: + w: 6 + h: 4 + sm: + w: 12 + h: 4 + xs: + w: 12 + h: 4 + xxs: + w: 12 + h: 4 + "Recently visited": + priority: 60 + breakpoints: + xl: + w: 6 + h: 4 + lg: + w: 6 + h: 4 + md: + w: 6 + h: 4 + sm: + w: 12 + h: 4 + xs: + w: 12 + h: 4 + xxs: + w: 12 + h: 4 + "Top visited": + priority: 50 + breakpoints: + xl: + w: 6 + h: 4 + lg: + w: 6 + h: 4 + md: + w: 6 + h: 4 + sm: + w: 12 + h: 4 + xs: + w: 12 + h: 4 + xxs: + w: 12 + h: 4 diff --git a/docs/dynamic-plugins/migrating-config-to-new-frontend-system.md b/docs/dynamic-plugins/migrating-config-to-new-frontend-system.md index c1afb10dbd..e9f982f361 100644 --- a/docs/dynamic-plugins/migrating-config-to-new-frontend-system.md +++ b/docs/dynamic-plugins/migrating-config-to-new-frontend-system.md @@ -8,17 +8,11 @@ This guide helps **operators and platform administrators** customize Red Hat Dev ## Default: new frontend system (NFS) -RHDH ships the Backstage new frontend system (`packages/app`) by default. Enable standard Module Federation on the **backend** deployment so dynamic frontend plugins load correctly (OpenShift, Helm, Operator, [rhdh-local](https://github.com/redhat-developer/rhdh-local), or any environment where the backend runs as a container): - -| Setting | How to apply | Purpose | -| --- | --- | --- | -| `ENABLE_STANDARD_MODULE_FEDERATION=true` | Environment variable on the backend container only | Enables the backend to serve standard Module Federation assets for dynamic frontend plugins. Without this, RHDH disables that service. | - -Example environment variable for the RHDH backend pod or deployment: - -```bash -ENABLE_STANDARD_MODULE_FEDERATION=true -``` +RHDH ships the Backstage new frontend system (`packages/app`) by default. The backend +serves standard Module Federation assets for dynamic frontend plugins out of the box — +no configuration needed (OpenShift, Helm, Operator, +[rhdh-local](https://github.com/redhat-developer/rhdh-local), or any environment where +the backend runs as a container). ## Who should read this @@ -28,7 +22,7 @@ ENABLE_STANDARD_MODULE_FEDERATION=true ## Prerequisites -- RHDH is running with the new frontend system enabled — see [Transition: the new frontend system is not the default yet](#transition-the-new-frontend-system-is-not-the-default-yet) above. +- RHDH is running with the new frontend system — see [Default: new frontend system (NFS)](#default-new-frontend-system-nfs) above. - You understand where your deployment stores `dynamic-plugins.yaml` and `app-config` — see [Installing Plugins](installing-plugins.md) and the [Red Hat product documentation](https://docs.redhat.com/en/documentation/red_hat_developer_hub/) for Helm and Operator paths. - Installed plugins support the new frontend system. Configuration alone cannot add UI that a plugin does not register as an extension. diff --git a/docs/e2e-tests/local-e2e-harness.md b/docs/e2e-tests/local-e2e-harness.md index 7369bc88b2..2e1f9c23cb 100644 --- a/docs/e2e-tests/local-e2e-harness.md +++ b/docs/e2e-tests/local-e2e-harness.md @@ -10,11 +10,6 @@ Run real Playwright E2E against RHDH **without** an OpenShift/Kubernetes cluster container images — a single `run` that boots the backend and the NFS frontend dev server in-process and drives a browser against them. -The harness targets the NFS frontend (`packages/app`, Tier B): it is what RHDH ships -today, and **the existing Playwright specs already target it**, so they run unmodified. -Dynamic frontend plugins load via standard Module Federation when the backend is started -with `ENABLE_STANDARD_MODULE_FEDERATION=true`. - The guest-auth + in-memory-SQLite overlay `app-config.local-e2e.yaml` is layered on top of `app-config.yaml`. Guest sign-in must be configured explicitly — the auth backend otherwise rejects guest with _"you must … configure the auth backend to support guest @@ -23,27 +18,25 @@ sign in."_ ### 1. Populate `dynamic-plugins-root` (one-time) Run the same script CI uses — it installs the harness plugin set -(`e2e-tests/local-harness/dynamic-plugins.yaml`) from the public OCI registry (ghcr) -via `install-dynamic-plugins` + skopeo, pinned to the same CLI version as CI. No -source build needed; works from a fresh clone. Requires skopeo (preinstalled in CI; -`brew install skopeo` on macOS): +(`e2e-tests/local-harness/dynamic-plugins.yaml`) from the public OCI registry (quay.io) +via `install-dynamic-plugins` + skopeo, with `{{inherit}}` resolved against the full +`dynamic-plugins.default.yaml` at the repo root (same as Helm/CI `includes`). Set +`CATALOG_INDEX_IMAGE` to extract the full catalog DPDY from a catalog-index OCI image +instead. Pinned to the same CLI version as CI. No source build needed; works from a +fresh clone. Requires skopeo (preinstalled in CI; `brew install skopeo` on macOS): ```bash ./e2e-tests/local-harness/populate.sh ``` -Alternatives: +Extract from a specific catalog index image (overrides repo-root DPDY with the full +catalog extracted from that image): -- **Catalog index** — the index's `dynamic-plugins.default.yaml` references the core - plugins by local `./dynamic-plugins/dist/…` paths that only exist after a source - build, so on a fresh clone most plugins are skipped. Use only after building - `dynamic-plugins` from source (main -> `:latest`; release branches -> the matching - `:1.y` tag): +```bash +CATALOG_INDEX_IMAGE=quay.io/rhdh/plugin-catalog-index:2.0 ./e2e-tests/local-harness/populate.sh +``` - ```bash - CATALOG_INDEX_IMAGE=quay.io/rhdh/plugin-catalog-index:latest \ - npx @red-hat-developer-hub/cli-module-install-dynamic-plugins install dynamic-plugins-root - ``` +Alternatives: - **Offline from-source** (frontend plugins only; requires a reconciled workspace — see "Known issues"): @@ -56,11 +49,11 @@ Alternatives: ### 2. Run ```bash -yarn --cwd e2e-tests e2e:legacy-local +yarn --cwd e2e-tests e2e:local ``` -Playwright (`playwright.legacy-local.config.ts`) boots the backend and the legacy app -dev server with `app-config.yaml` + `app-config.dynamic-plugins.yaml` + +Playwright (`playwright.local.config.ts`) boots the backend and the NFS app dev server +with `app-config.yaml` + `app-config.dynamic-plugins.yaml` + `app-config.local-e2e.yaml`. A `globalSetup` first fails fast with the populate command if `dynamic-plugins-root` has no plugins. @@ -72,66 +65,89 @@ spec file to `testMatch`; if the test needs extra plugins, add them (with their ### Verified -With plugins populated, the legacy app renders the full production RHDH UI off-cluster -(branding, sidebar, global header, and Quick Access from the dynamic plugins). The -existing specs **pass unmodified**: - -- `guest-signin-happy-path` — all three tests: home page (dynamic-home-page plugin), - Settings and Sign-out (navigation via the global-header profile dropdown, using the - plugin's canonical `pluginConfig` merged through the generated - `dynamic-plugins-root/app-config.dynamic-plugins.yaml`, exactly as in-cluster). -- `learning-path-page` — renders from the static fallback data bundled with - `packages/app`; the "References" sidebar group mirrors the CI menu customization via - `app-config.local-e2e.yaml`. +With plugins populated, the NFS app renders the full production RHDH UI off-cluster +(branding, sidebar, the home-page widgets from the dynamic plugins, and the full +GlobalHeader — CompanyLogo, Search, StarredDropdown, ApplicationLauncherDropdown, +HelpDropdown, NotificationButton, and ProfileDropdown all render — see "Known issues" +for how that's wired). + +- `guest-signin-happy-path` — the `@cluster-free` test (home page: Welcome heading, + Search and Starred Entities, all dynamic-home-page-plugin widgets, plus Settings and + Sign-out via the GlobalHeader's profile menu). A fourth, untagged test in the same + file exercises Quick Access against the real `/developer-hub` proxy; it only runs in + the full cluster-based CI suite (see "Known issues"). +- `learning-path-page` — navigates directly to `/learning-paths` (the route the + Onboarding home-page widget's CTA links to) and renders from the static fallback data + bundled with the plugin. NFS's sidebar has no config-driven nested group to reach it + through (see `plugins/frontend/sidebar` below). - `instance-health-check` — `GET /healthcheck` against the frontend origin. The app dev server proxies `/healthcheck` to the backend (`proxy` field in `packages/app/package.json`), mirroring the single-origin production container where the backend serves both the app and the health endpoint. - `smoke-test` — guest sign-in plus the home-page welcome heading (dynamic-home-page plugin); its readiness poll uses the same proxied `/healthcheck`. -- `home-page-customization` — all three tests. The CI home-page card customization - (Placeholder/Markdown/Featured Docs/Random Joke/Top + Recently Visited, from - `.ci/pipelines/resources/config_map/dynamic-plugins-config.yaml`) is mirrored in - `app-config.local-e2e.yaml`; the Random Joke card fetches jokes from the public - Official Joke API in the browser, so it needs outbound network access. -- `plugins/frontend/sidebar` — sidebar menu customization (References group, Test - enabled/nested items, techdocs Favorites → Docs and Test_i items) mirrored from the - same CI configmap into `app-config.local-e2e.yaml`. The `/docs` index page needs the - techdocs frontend OCI plugin in the harness set (its route/menu config already lives - in the static `app-config.dynamic-plugins.yaml`). -- `settings` — language toggle (needs the CI `i18n.locales` list mirrored in the - overlay) plus the identity card ownership ("Guest User, team-a"). CI gets those - entities from Keycloak ingestion; the harness ingests the equivalent minimal - User/Group pair from `e2e-tests/local-harness/guest-ownership-entities.yaml` via a - `catalog.locations` file entry in the overlay (file targets resolve relative to the - backend cwd, `packages/backend`). The guest sign-in resolver picks the entity up and - issues ownership refs including `team-a` exactly as in-cluster. +- `home-page-customization` — the Quick Access, Featured Docs, Top Visited, and + Recently Visited widgets, using the `home-page-layout:home/dynamic-homepage-layout` + `app.extensions` customization mirrored from + `.ci/pipelines/resources/config_map/dynamic-plugins-config.yaml` into + `app-config.local-e2e.yaml`. NFS home-page cards are a fixed set of built-in widgets + (`home-page-widget:home/*`) — the legacy Placeholder/Markdown/Random Joke mount-point + cards have no NFS equivalent and are no longer asserted. Its `@cluster-free` "Quick + Access" assertion only checks the card's title text, not real link data (see "Known + issues" for why); the file's other, untagged "Verify Customized Quick Access" test + covers the real link data and only runs in full CI. +- `plugins/frontend/sidebar` — the `@cluster-free` test only verifies flat-nav behavior: + the Docs (TechDocs) sidebar item navigates to the TechDocs index page (NFS + `PageBlueprint` title: "Docs", not the legacy OFS `pageWrapper.title` + "Documentation"). NFS's sidebar (`packages/app/src/modules/nav/Sidebar.tsx`) is a + fixed, code-defined flat nav with no config-driven nested-group equivalent of the + legacy References/Favorites `menuItems`. It stops at the index page — this + harness's catalog has no `techdocs-ref`-annotated entities (see "Known issues"), + so there's nothing to click into. The file's other, untagged "Verify Docs entity + page renders real content" test opens a real entity's docs and checks for actual + content; it only runs in full CI, where `catalog-entities/components/showcase.yaml`/ + `community-plugins.yaml` provide real `techdocs-ref` entities. - `plugins/user-settings-info-card` — the CI `buildInfo` card customization ("RHDH - Build info") mirrored in the overlay. -- `plugins/application-provider` and `plugins/application-listener` — the - application-provider-test / application-listener-test OCI plugins with the same - pluginConfig CI uses in its Helm values (`values_showcase.yaml`); they are OCI-only - builds, not part of the repo's dynamic-plugins source tree. + Build info") mirrored in the overlay. Reaches the Settings page via the same + `goToSettingsPage()` helper `settings.spec.ts` uses, through the GlobalHeader's + profile menu. +- `settings.spec.ts` — language toggle (needs `api:app/app-language.availableLanguages` + in `app.extensions`, not just `i18n.locales`), French label switching, pin-sidebar + toggle, and identity-card ownership ("Guest User, team-a"). NFS sidebar page titles + stay in upstream English ("Home") even after switching AppLanguageApi to French — + only GlobalHeader chrome translates; the final assertion checks "Home", not + `menuItem.home` ("Accueil"). + +Not enablable yet: -Not enablable yet: `plugins/licensed-users-info-backend` — the -`licensed-users-info-backend` plugin is not published to the overlays OCI registry +- `plugins/application-provider` and `plugins/application-listener` — the + application-provider-test / application-listener-test OCI plugins (pinned in + `values_showcase.yaml` for cluster-based CI) only publish an OFS ("." Module + Federation) entry point, no NFS/alpha extensions. `application/provider` and + `application/listener` are rendered exclusively by `packages/app-legacy`'s + `ApplicationProvider.tsx`/`ApplicationListener.tsx`, which the NFS `packages/app` has + no equivalent for — so neither spec can currently pass here. Not installed in the + harness; re-add once NFS support exists for either the fixture plugins or a generic + provider/listener renderer in `packages/app`. +- `plugins/licensed-users-info-backend` — the + `licensed-users-info-backend` plugin is not published to the overlays OCI registry (ghcr) and only exists as a `./dynamic-plugins/dist` source build. ## CI `.github/workflows/e2e-cluster-free.yaml` runs this harness on GitHub Actions in a cluster-free phase: it installs deps + skopeo, populates `dynamic-plugins-root` via -`./e2e-tests/local-harness/populate.sh` (the harness plugin set from the public OCI -registry, ghcr), then runs `yarn e2e:legacy-local`. No cluster or container image is -built. It triggers on `e2e-tests/**` and `app-config*.yaml` changes; the scope can -widen to `packages/app/**` / `packages/backend/**` once it is proven stable. +`./e2e-tests/local-harness/populate.sh` (full repo-root `dynamic-plugins.default.yaml` ++ harness plugin set from quay.io), then runs `yarn e2e:local`. No cluster or container +image is built. It triggers on `e2e-tests/**` and `app-config*.yaml` changes; the scope +can widen to `packages/app/**` / `packages/backend/**` once it is proven stable. ## NFS frontend (`packages/app`) -The harness targets the NFS frontend (`packages/app`). The backend boots with -`ENABLE_STANDARD_MODULE_FEDERATION=true` so dynamic frontend plugins load via Module +The harness targets the NFS frontend (`packages/app`). The backend serves standard +Module Federation assets by default, so dynamic frontend plugins load via Module Federation remotes from the backend (see `packages/backend/src/index.ts` and -`playwright.legacy-local.config.ts`). +`playwright.local.config.ts`). ## vs. rhdh-local @@ -149,20 +165,88 @@ just `run`), which is why this harness boots the dev servers directly instead. dependency versions), backend dynamic-plugin builds fail with version-mismatch errors and yarn may not surface workspace bins. Run `yarn install` first. The `install-dynamic-plugins` populate path avoids building from source and is unaffected. -- **Re-run `populate.sh` after changing the harness plugin set.** The `pluginConfig` - blocks in `e2e-tests/local-harness/dynamic-plugins.yaml` (e.g. the global-header - mount points) only take effect through the generated - `dynamic-plugins-root/app-config.dynamic-plugins.yaml`, which the webServer loads - last. A stale populate leaves plugins loaded but unconfigured (the header renders - empty). -- **Specs that need CI test data are not enabled yet.** `settings.spec.ts` asserts - ownership entities ("Guest User, team-a") that come from catalog locations in the CI - config map; `home-page-customization.spec.ts` needs the home-page card customization - from `.ci/pipelines/resources/config_map/dynamic-plugins-config.yaml`. Enabling them - means mirroring that data/config into the harness overlay. +- **Re-run `populate.sh` after changing the harness plugin set.** Any `pluginConfig` + blocks in `e2e-tests/local-harness/dynamic-plugins.yaml` only take effect through the + generated `dynamic-plugins-root/app-config.dynamic-plugins.yaml`, which the webServer + loads last. A stale populate leaves plugins loaded but unconfigured/outdated. - **Live-external-service specs** (real k8s cluster, GitHub org, Quay, Tekton, Keycloak) still need those services or mocks; this harness covers UI/plugin-rendering scenarios that don't require live external infra. - **`janus-cli` / `backstage-cli`** live in the repo-root `node_modules/.bin`, which yarn does not surface for the `app`/`backend` workspaces, so the webServer commands invoke them directly with the root `.bin` prepended to `PATH`. +- **`VisitsStorageApi` 404s under this config, pre-dating NFS.** `app-config.local-e2e.yaml` + sets `userSettings.persistence: browser` (added in `#4020`, before the NFS migration, + to opt CI out of database storage, and mirrored in the CI ConfigMap), which means + `@backstage/plugin-user-settings-backend` is never loaded (see + `packages/backend/src/modules/userSettings.ts`). `VisitsStorageApi` calls the backend + regardless, so `/api/user-settings/multiget`/`/api/user-settings/buckets/...` 404 — + in a production build that's just a background error, but under this harness's dev + server the unhandled rejection trips webpack's react-refresh error overlay (a + full-screen transparent `