diff --git a/.changeset/nfs-shell-and-scaffolder-migration.md b/.changeset/nfs-shell-and-scaffolder-migration.md new file mode 100644 index 000000000..a474f35ab --- /dev/null +++ b/.changeset/nfs-shell-and-scaffolder-migration.md @@ -0,0 +1,28 @@ +--- +'@openchoreo/backstage-plugin': minor +'@openchoreo/backstage-plugin-openchoreo-observability': minor +'@openchoreo/backstage-plugin-platform-engineer-core': minor +'@openchoreo/backstage-plugin-react': minor +'@openchoreo/backstage-portal-app': minor +--- + +Complete the New Frontend System migration for the portal shell and +distribute scaffolder field extensions through the base plugin. + +**Portal**: `convertLegacyAppRoot` and `Root.tsx` are gone. Themes, icons, +sidebar (`NavContentBlueprint`), provider stack, and every route now +ship as NFS blueprints. + +**Adopter-facing additions**: + +- `@openchoreo/backstage-plugin/alpha` — `execTerminalPage`, 32 + `FormFieldBlueprint`s for OC template fields, plus new component + exports (`ScaffolderPreselectionProvider`, `EntityWarningStrip`, + `ForeignCardsSection`) +- `@openchoreo/backstage-plugin-openchoreo-observability/alpha` — + `costInsightsPage` with sidebar auto-discovery +- `@openchoreo/backstage-plugin-platform-engineer-core/alpha` — + `platformOverviewPage` with sidebar auto-discovery; `PlatformOverviewPage` + source moved from portal-app +- `@openchoreo/backstage-plugin-react` — `useQueryParams` (backwards-compat + re-export left in `@openchoreo/backstage-plugin`) diff --git a/app-config.production.yaml b/app-config.production.yaml index c406d18e1..a3042d605 100644 --- a/app-config.production.yaml +++ b/app-config.production.yaml @@ -22,8 +22,14 @@ app: - runtime: { title: Runtime, contentOrder: natural } - analysis: { title: Analysis, contentOrder: natural } - external: { title: External } - # Suppress upstream cards that duplicate OC layouts. + # Suppress upstream cards that duplicate OC layouts + default themes so + # our openchoreo-* themes win the theme picker. extensions: + - theme:app/light: false + - theme:app/dark: false + # `page:kubernetes` needs an entity context (throws standalone); we + # want only the entity-page tab from the kubernetes plugin. + - page:kubernetes: false - entity-card:catalog/about: false - entity-card:catalog/links: false - entity-card:catalog/labels: false @@ -36,6 +42,9 @@ app: - entity-card:api-docs/provided-apis: false - entity-card:api-docs/consumed-apis: false - entity-card:api-docs/has-apis: false + # ApiDefinitionCard lives on the Definition tab; hide the standalone. + - entity-card:api-docs/definition: false + # Providers/Consumers rendered directly by ApiOverviewLayout. - entity-card:api-docs/providing-components: false - entity-card:api-docs/consuming-components: false # GitLab cards throw when GitLab annotations are absent. diff --git a/app-config.yaml b/app-config.yaml index 799764d71..b0caec239 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -33,8 +33,16 @@ app: - runtime: { title: Runtime, contentOrder: natural } - analysis: { title: Analysis, contentOrder: natural } - external: { title: External } - # Suppress upstream cards that duplicate OC layouts. + # Suppress upstream cards that duplicate OC layouts + default themes so + # our openchoreo-* themes win the theme picker. extensions: + - theme:app/light: false + - theme:app/dark: false + # `page:kubernetes` is the plugin's standalone /kubernetes page — it + # calls useEntity() inside its router and throws when opened without + # an entity context. We only want the plugin's entity-page tab, not + # this broken top-level page. + - page:kubernetes: false - entity-card:catalog/about: false - entity-card:catalog/links: false - entity-card:catalog/labels: false @@ -47,6 +55,12 @@ app: - entity-card:api-docs/provided-apis: false - entity-card:api-docs/consumed-apis: false - entity-card:api-docs/has-apis: false + # ApiDefinitionCard already lives on the Definition tab; hide the + # standalone card on API Overview to avoid duplication. + - entity-card:api-docs/definition: false + # Providers/Consumers rendered directly by ApiOverviewLayout, so + # suppress the auto-contributed cards to prevent duplicates in + # ForeignCardsSection. - entity-card:api-docs/providing-components: false - entity-card:api-docs/consuming-components: false # GitLab cards throw when GitLab annotations are absent. diff --git a/packages/portal-app/src/apis.test.ts b/packages/portal-app/src/apis.test.ts index 00cc6911b..a47e1bc3c 100644 --- a/packages/portal-app/src/apis.test.ts +++ b/packages/portal-app/src/apis.test.ts @@ -1,27 +1,15 @@ /** * Smoke test for the API factories registered in `apis.ts`. * - * The render-the-whole-app test in App.test.tsx exercises most factories - * indirectly, but the perch-agent factory is wired through a route - * the smoke render never visits, so its body shows up as 0% covered on - * the codecov diff. This file calls each factory directly with the - * minimum dependency surface so the body is executed. - * - * We only assert "factory returned an instance" — full client behavior - * is covered by each plugin's own tests. - * - * Under NFS, `openchoreo-ci`, `openchoreo-workflows`, and the - * `catalog-graph` override own their API factories via `ApiBlueprint` - * inside their `/alpha` plugins / `customOverrides.tsx`, so they are - * NOT in this app-scoped `apis` array. Their factory bodies are covered - * by the plugins' own tests. + * OpenChoreo-specific fetch / permission / auth factories now ship from + * `@openchoreo/backstage-plugin` as ApiBlueprints, so they're not in this + * app-scoped `apis` array — covered by the plugin's own tests. */ import { AnyApiFactory, fetchApiRef, storageApiRef, } from '@backstage/core-plugin-api'; -import { permissionApiRef } from '@backstage/plugin-permission-react'; import { visitsApiRef } from '@backstage/plugin-home'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { @@ -30,10 +18,7 @@ import { } from '@openchoreo/backstage-plugin-openchoreo-portal-assistant'; import { apis } from './apis'; -import { openChoreoAuthApiRef } from './apis/authRefs'; -// Minimal stubs — none of the factories under test inspect dep state at -// construction time beyond holding the reference. const stubDiscovery = { getBaseUrl: async () => 'http://localhost' } as any; const stubFetch = { fetch: globalThis.fetch ?? (() => undefined) } as any; const stubIdentity = { @@ -54,9 +39,6 @@ function findFactory( } function invoke(factory: AnyApiFactory, deps: Record) { - // ``factory`` is an internal type — call its ``factory`` member with - // the requested deps. The deps object is whatever ``factory.deps`` - // declares; we provide stubs keyed by the same names. return (factory as any).factory(deps); } @@ -65,9 +47,6 @@ describe('apis registry', () => { const ids = apis.map(f => f.api.id); for (const ref of [ scmIntegrationsApiRef, - permissionApiRef, - fetchApiRef, - openChoreoAuthApiRef, visitsApiRef, storageApiRef, perchAgentApiRef, @@ -76,6 +55,12 @@ describe('apis registry', () => { } }); + it('does NOT register OC-specific fetch/permission/auth factories (base plugin owns them)', () => { + const ids = apis.map(f => f.api.id); + expect(ids).not.toContain(fetchApiRef.id); + // permission and openchoreo-auth are also owned by the base plugin now + }); + it('builds the PerchAgentClient via its factory', () => { const f = findFactory(apis, perchAgentApiRef); const instance = invoke(f, { diff --git a/packages/portal-app/src/apis.ts b/packages/portal-app/src/apis.ts index bdd63f61e..774146055 100644 --- a/packages/portal-app/src/apis.ts +++ b/packages/portal-app/src/apis.ts @@ -8,37 +8,26 @@ import { configApiRef, createApiFactory, discoveryApiRef, - oauthRequestApiRef, errorApiRef, identityApiRef, fetchApiRef, storageApiRef, } from '@backstage/core-plugin-api'; -import { OAuth2 } from '@backstage/core-app-api'; import { VisitsWebStorageApi, visitsApiRef } from '@backstage/plugin-home'; - -// No explicit default theme id is seeded into localStorage. Backstage's -// user-settings panel renders a built-in Auto button that picks the first -// registered dark or light theme based on the OS `prefers-color-scheme` -// preference, which is exactly the behavior we want for new users. import { UserSettingsStorage } from '@backstage/plugin-user-settings'; -import { permissionApiRef } from '@backstage/plugin-permission-react'; -import { OpenChoreoFetchApi } from './apis/OpenChoreoFetchApi'; -import { OpenChoreoPermissionApi } from './apis/OpenChoreoPermissionApi'; -import { openChoreoAuthApiRef } from './apis/authRefs'; -// NOTE: ``perchAgentApiRef`` is also declared on -// ``openchoreoPerchPlugin.apis`` in plugins/openchoreo-portal-assistant/src/plugin.ts. -// That declaration is NOT picked up by the app at runtime because the plugin -// exports plain React components — it never registers a routable or -// component extension, so Backstage's plugin loader never visits its -// ``apis`` array. The app-level factory below is the one actually wired in; -// removing it causes ``NotImplementedError: No implementation available for -// apiRef{plugin.openchoreo-portal-assistant.service}`` in AssistantDrawerProvider. +// NOTE: `perchAgentApiRef` is also declared on `openchoreoPerchPlugin.apis` +// in plugins/openchoreo-portal-assistant/src/plugin.ts. That declaration is +// NOT picked up at runtime because the plugin only exports plain React +// components. Removing this app-level factory causes NotImplementedError +// in AssistantDrawerProvider. import { perchAgentApiRef, PerchAgentClient, } from '@openchoreo/backstage-plugin-openchoreo-portal-assistant'; +// OpenChoreo fetch/permission/auth factories are contributed by +// `@openchoreo/backstage-plugin` (base) as ApiBlueprints — no manual +// registration needed here. export const apis: AnyApiFactory[] = [ createApiFactory({ api: scmIntegrationsApiRef, @@ -46,73 +35,6 @@ export const apis: AnyApiFactory[] = [ factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), }), ScmAuth.createDefaultApiFactory(), - - // Custom PermissionApi that injects IDP token for OpenChoreo authorization - // This is needed because Backstage's default PermissionClient doesn't allow - // custom headers to be injected (it uses cross-fetch directly) - createApiFactory({ - api: permissionApiRef, - deps: { - configApi: configApiRef, - discoveryApi: discoveryApiRef, - identityApi: identityApiRef, - oauthApi: openChoreoAuthApiRef, - }, - factory: ({ configApi, discoveryApi, identityApi, oauthApi }) => - new OpenChoreoPermissionApi({ - config: configApi, - discovery: discoveryApi, - identity: identityApi, - oauthApi, - }), - }), - - // Custom FetchApi that automatically injects auth tokens - // This wraps all fetch calls to include Backstage token + IDP token - // When openchoreo.features.auth.enabled is false, IDP token injection is skipped - createApiFactory({ - api: fetchApiRef, - deps: { - identityApi: identityApiRef, - oauthApi: openChoreoAuthApiRef, - configApi: configApiRef, - }, - factory: ({ identityApi, oauthApi, configApi }) => - new OpenChoreoFetchApi(identityApi, oauthApi, configApi), - }), - - // OpenChoreo Auth provider - works with any OIDC-compliant IDP - createApiFactory({ - api: openChoreoAuthApiRef, - deps: { - discoveryApi: discoveryApiRef, - oauthRequestApi: oauthRequestApiRef, - configApi: configApiRef, - }, - factory: ({ discoveryApi, oauthRequestApi, configApi }) => { - const env = - configApi.getOptionalString('auth.environment') ?? 'development'; - const scopeStr = configApi.getOptionalString( - 'openchoreo.features.auth.scope', - ); - const scopes = scopeStr?.split(/\s+/).filter(Boolean) ?? []; - const defaultScopes = scopes.length - ? scopes - : ['openid', 'profile', 'email']; - return OAuth2.create({ - discoveryApi, - oauthRequestApi, - configApi, - provider: { - id: 'openchoreo-auth', - title: 'OpenChoreo', - icon: () => null, - }, - environment: env, - defaultScopes, - }); - }, - }), createApiFactory({ api: visitsApiRef, deps: { @@ -122,7 +44,6 @@ export const apis: AnyApiFactory[] = [ factory: ({ identityApi, errorApi }) => VisitsWebStorageApi.create({ identityApi, errorApi }), }), - // User settings storage - enables centralized storage for starred entities and preferences createApiFactory({ api: storageApiRef, deps: { @@ -133,10 +54,6 @@ export const apis: AnyApiFactory[] = [ }, factory: deps => UserSettingsStorage.create(deps), }), - - // Assistant Agent client (Perch). Mirrors the registration on - // openchoreoPerchPlugin.apis — see the import-site comment for why - // both exist. createApiFactory({ api: perchAgentApiRef, deps: { diff --git a/packages/portal-app/src/apis/customOverrides.tsx b/packages/portal-app/src/apis/customOverrides.tsx index a9c72831e..e9d89166f 100644 --- a/packages/portal-app/src/apis/customOverrides.tsx +++ b/packages/portal-app/src/apis/customOverrides.tsx @@ -9,9 +9,16 @@ import catalogGraphPluginAlphaBase from '@backstage/plugin-catalog-graph/alpha'; import catalogPluginAlphaBase from '@backstage/plugin-catalog/alpha'; +import catalogImportPluginAlphaBase from '@backstage/plugin-catalog-import/alpha'; import apiDocsPluginAlphaBase from '@backstage/plugin-api-docs/alpha'; import techdocsPluginAlphaBase from '@backstage/plugin-techdocs/alpha'; import scaffolderPluginAlphaBase from '@backstage/plugin-scaffolder/alpha'; +import searchPluginAlphaBase from '@backstage/plugin-search/alpha'; +import userSettingsPluginAlphaBase from '@backstage/plugin-user-settings/alpha'; +import { SearchContextProvider } from '@backstage/plugin-search-react'; +import { RequirePermission } from '@backstage/plugin-permission-react'; +import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +import { CustomGraphNode } from '@openchoreo/backstage-plugin-react'; import type { Entity } from '@backstage/catalog-model'; import { isTechDocsAvailable } from '@backstage/plugin-techdocs'; import { createFrontendModule } from '@backstage/frontend-plugin-api'; @@ -80,6 +87,15 @@ import { InvestigateLogButton } from '@openchoreo/backstage-plugin-openchoreo-po export const catalogGraphPluginAlpha = catalogGraphPluginAlphaBase.withOverrides({ extensions: [ + // Override `page:catalog-graph` to pass renderNode={CustomGraphNode}. + catalogGraphPluginAlphaBase.getExtension('page:catalog-graph').override({ + params: { + loader: () => + import('@backstage/plugin-catalog-graph').then(m => ( + + )), + }, + }), catalogGraphPluginAlphaBase.getExtension('api:catalog-graph').override({ params: defineParams => defineParams({ @@ -252,17 +268,55 @@ export const apiDocsPluginAlpha = apiDocsPluginAlphaBase.withOverrides({ .override({ params: { filter: hasApis }, }), + // /api-docs → host's CustomApiExplorerPage instead of upstream's default. + apiDocsPluginAlphaBase.getExtension('page:api-docs').override({ + params: { + // CustomApiExplorerPage mounts its own . + noHeader: true, + loader: () => + import('../components/catalog/CustomApiExplorerPage').then(m => ( + + )), + }, + }), + // Portal-only: raw OpenAPI YAML viewer instead of upstream's ApiDefinitionCard. + apiDocsPluginAlphaBase + .getExtension('entity-content:api-docs/definition') + .override({ + params: { + loader: async () => + import('../components/apiOverview/ApiOpenApiDefinitionTab').then( + m => , + ), + }, + }), ], }); -/** - * Override upstream's `entity-content:techdocs` (the "TechDocs" entity tab) - * to hide it unless the entity carries the `backstage.io/techdocs-ref` - * annotation. Upstream registers the tab with no filter — it appears on - * every entity page even when there's nothing to render. The portal has - * always hidden it via `isTechDocsAvailable(entity)` in the legacy - * `EntityPage.tsx`. - */ +// /catalog-import → host's CustomCatalogImportPage, gated by permission. +export const catalogImportPluginAlpha = + catalogImportPluginAlphaBase.withOverrides({ + extensions: [ + catalogImportPluginAlphaBase + .getExtension('page:catalog-import') + .override({ + params: { + // CustomCatalogImportPage mounts its own
. + noHeader: true, + loader: () => + import('../components/catalog/CustomCatalogImportPage').then( + m => ( + + + + ), + ), + }, + }), + ], + }); + +// Hide TechDocs entity tab unless `backstage.io/techdocs-ref` is set. export const techdocsPluginAlpha = techdocsPluginAlphaBase.withOverrides({ extensions: [ techdocsPluginAlphaBase.getExtension('entity-content:techdocs').override({ @@ -345,6 +399,39 @@ export const customAppModule = createFrontendModule({ ], }); +// /search → host's SearchPage (software-catalog only, kind dropdown filter). +export const searchPluginAlpha = searchPluginAlphaBase.withOverrides({ + extensions: [ + searchPluginAlphaBase.getExtension('page:search').override({ + params: { + noHeader: true, + loader: () => + import('../components/search/SearchPage').then(m => ( + {m.searchPage} + )), + }, + }), + ], +}); + +// /settings → legacy SettingsLayout chrome with three curated tabs +// (General / Access Control / Secrets). Bypasses upstream SubPageBlueprint +// collection. +export const userSettingsPluginAlpha = + userSettingsPluginAlphaBase.withOverrides({ + extensions: [ + userSettingsPluginAlphaBase.getExtension('page:user-settings').override({ + params: { + noHeader: true, + loader: () => + import('../components/settings/OpenChoreoUserSettingsPage').then( + m => , + ), + }, + }), + ], + }); + export const scaffolderPluginAlpha = scaffolderPluginAlphaBase.withOverrides({ extensions: [ // Override `page:scaffolder`'s loader to render the host's diff --git a/packages/portal-app/src/appModule.tsx b/packages/portal-app/src/appModule.tsx new file mode 100644 index 000000000..4ff98d9ed --- /dev/null +++ b/packages/portal-app/src/appModule.tsx @@ -0,0 +1,101 @@ +import { + ApiBlueprint, + createFrontendModule, +} from '@backstage/frontend-plugin-api'; +import { + AppRootWrapperBlueprint, + IconBundleBlueprint, + NavContentBlueprint, + ThemeBlueprint, +} from '@backstage/plugin-app-react'; +import { + EntityContentBlueprint, + EntityContentLayoutBlueprint, +} from '@backstage/plugin-catalog-react/alpha'; +import { AssistantDrawerProvider } from '@openchoreo/backstage-plugin-openchoreo-portal-assistant'; +import { OpenChoreoQueryProvider } from '@openchoreo/backstage-plugin-react'; +import { apis } from './apis'; +import { LEGACY_KIND_ICONS } from './kindIcons'; +import { appThemes } from './themes'; +import { PortalNavContent } from './components/Root/PortalNavContent'; +import { ScaffolderPreselectionProvider } from '@openchoreo/backstage-plugin'; + +const apiExtensions = apis.map(factory => + ApiBlueprint.make({ + name: factory.api.id, + params: defineParams => defineParams(factory), + }), +); + +const iconBundle = IconBundleBlueprint.make({ + name: 'kind-icons', + params: { icons: LEGACY_KIND_ICONS }, +}); + +const themeExtensions = appThemes.map(theme => + ThemeBlueprint.make({ + name: theme.id, + params: { theme }, + }), +); + +const navContent = NavContentBlueprint.make({ + params: { component: PortalNavContent }, +}); + +// Wraps the app root so page overrides attaching under upstream plugin +// scopes (page:catalog/entity, page:api-docs, ...) inherit query context. +const openChoreoQueryWrapper = AppRootWrapperBlueprint.make({ + name: 'openchoreo-query', + params: { component: OpenChoreoQueryProvider }, +}); + +const scaffolderPreselectionWrapper = AppRootWrapperBlueprint.make({ + name: 'scaffolder-preselection', + params: { component: ScaffolderPreselectionProvider }, +}); + +const assistantDrawerWrapper = AppRootWrapperBlueprint.make({ + name: 'assistant-drawer', + params: { component: AssistantDrawerProvider }, +}); + +// Portal-only. Adopters get vanilla upstream api-docs behavior on API pages. +const apiTryOutEntityContent = EntityContentBlueprint.make({ + name: 'api-try-out', + params: { + path: '/try-out', + title: 'Try Out', + group: 'runtime', + filter: { kind: 'api' }, + loader: () => + import('@openchoreo/backstage-plugin').then(m => ), + }, +}); + +const apiOverviewLayout = EntityContentLayoutBlueprint.make({ + name: 'api-overview', + params: { + filter: { kind: 'api' }, + loader: () => + import('./components/apiOverview/ApiOverviewLayout').then(m => m.default), + }, +}); + +// Attached to pluginId 'app'. Required for theme / icon / nav-content / +// root-wrapper extensions since those inputs on plugin-app are `internal` +// and only accept contributions from a module of the 'app' plugin. +export const appModule = createFrontendModule({ + pluginId: 'app', + extensions: [ + ...apiExtensions, + iconBundle, + ...themeExtensions, + navContent, + openChoreoQueryWrapper, + scaffolderPreselectionWrapper, + assistantDrawerWrapper, + apiTryOutEntityContent, + apiOverviewLayout, + ], +}); diff --git a/packages/portal-app/src/components/DynamicSignInPage.tsx b/packages/portal-app/src/components/DynamicSignInPage.tsx index 08eb42800..934d80669 100644 --- a/packages/portal-app/src/components/DynamicSignInPage.tsx +++ b/packages/portal-app/src/components/DynamicSignInPage.tsx @@ -1,7 +1,7 @@ import { SignInPage } from '@backstage/core-components'; import { configApiRef, useApi } from '@backstage/core-plugin-api'; import type { SignInPageProps } from '@backstage/plugin-app-react'; -import { openChoreoAuthApiRef } from '../apis/authRefs'; +import { openChoreoAuthApiRef } from '@openchoreo/backstage-plugin'; import { brandName, useBranding } from '../branding'; /** diff --git a/packages/portal-app/src/components/Root/PortalNavContent.tsx b/packages/portal-app/src/components/Root/PortalNavContent.tsx new file mode 100644 index 000000000..8d4103e0d --- /dev/null +++ b/packages/portal-app/src/components/Root/PortalNavContent.tsx @@ -0,0 +1,212 @@ +import { useEffect } from 'react'; +import { makeStyles, Tooltip, IconButton } from '@material-ui/core'; +import ExitToAppIcon from '@material-ui/icons/ExitToApp'; +import MenuIcon from '@material-ui/icons/Menu'; +import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; +import ChevronRightIcon from '@material-ui/icons/ChevronRight'; +import SearchIcon from '@material-ui/icons/Search'; +import GroupIcon from '@material-ui/icons/People'; +import { identityApiRef, useApi } from '@backstage/core-plugin-api'; +import { + Sidebar, + sidebarConfig, + SidebarDivider, + SidebarGroup, + SidebarItem, + SidebarScrollWrapper, + SidebarSpace, + useSidebarOpenState, + Link, +} from '@backstage/core-components'; +import { + Settings as SidebarSettings, + UserSettingsSignInAvatar, +} from '@backstage/plugin-user-settings'; +import { + SidebarSearchModal, + SearchModalProvider, + useSearchModal, +} from '@backstage/plugin-search'; +import { MyGroupsSidebarItem } from '@backstage/plugin-org'; +import type { + NavContentComponentProps, + NavContentNavItem, +} from '@backstage/plugin-app-react'; +import { queryClient } from '@openchoreo/backstage-plugin-react'; +import LogoFull from './LogoFull'; +import LogoIcon from './LogoIcon'; +import { CustomSearchModal } from '../search/CustomSearchModal'; + +const isMac = + typeof navigator !== 'undefined' && + /Mac|iPhone|iPad/.test(navigator.userAgent); +const searchShortcutLabel = `Search (${isMac ? '⌘K' : 'Ctrl+K'})`; + +// Global CSS tweaks mounted once with the sidebar. +const useSearchModalStyles = makeStyles(theme => ({ + '@global': { + // Search Modal: cap width lg (1280px) -> md (960px). + '.MuiDialog-root[aria-label="Search Modal"] .MuiDialog-paperWidthLg': { + maxWidth: 960, + }, + // BUI card titles: shrink to match legacy MUI sizing. + '.bui-CardHeader h3.bui-Text': { + fontSize: '1rem', + }, + // BUI table headers: soften from stark black. + '.bui-TableHead, .bui-TableHeadContent': { + color: theme.palette.text.secondary, + }, + }, +})); + +const useSidebarLogoStyles = makeStyles(theme => ({ + root: { + width: sidebarConfig.drawerWidthClosed, + height: 2.25 * sidebarConfig.logoHeight, + display: 'flex', + flexFlow: 'row nowrap', + alignItems: 'center', + position: 'relative', + }, + link: { + width: sidebarConfig.drawerWidthClosed, + marginLeft: 24, + }, + toggleButton: { + display: 'none', + position: 'absolute', + right: -20, + top: '50%', + transform: 'translateY(-50%)', + backgroundColor: theme.palette.background.paper, + border: `1px solid ${theme.palette.divider}`, + boxShadow: theme.shadows[2], + zIndex: 1000, + '&:hover': { + backgroundColor: theme.palette.action.hover, + }, + }, +})); + +const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); + const { isOpen, setOpen } = useSidebarOpenState(); + return ( +
+ + {isOpen ? : } + + setOpen(!isOpen)} + size="small" + aria-label={isOpen ? 'Collapse sidebar' : 'Expand sidebar'} + > + {isOpen ? : } + +
+ ); +}; + +const KeyboardShortcutSearchToggler = () => { + const { setOpen } = useSearchModal(); + useEffect(() => { + const handler = (event: KeyboardEvent) => { + if ((event.metaKey || event.ctrlKey) && event.key === 'k') { + event.preventDefault(); + setOpen(true); + } + }; + window.addEventListener('keydown', handler); + return () => window.removeEventListener('keydown', handler); + }, [setOpen]); + return null; +}; + +const SignOutButton = () => { + const identityApi = useApi(identityApiRef); + const handleSignOut = async () => { + await identityApi.signOut(); + // Clear per-user cached BFF responses before redirect. + queryClient.clear(); + window.location.href = '/'; + }; + return ( + + ); +}; + +const NavItemLink = ({ item }: { item: NavContentNavItem }) => ( + item.icon} + to={item.href} + text={item.title} + key={item.node.spec.id} + /> +); + +// Curated sidebar: only these pages appear. Other auto-discovered pages +// remain routable but have no nav entry. +const HOME_ID = 'page:openchoreo-portal-app/home'; +const CATALOG_ID = 'page:catalog'; +const PLATFORM_ID = 'page:platform-engineer-core/platform-overview'; +const COST_INSIGHTS_ID = 'page:openchoreo-observability/cost-insights'; +const APIS_ID = 'page:api-docs'; +const CREATE_ID = 'page:scaffolder'; + +export function PortalNavContent({ navItems }: NavContentComponentProps) { + useSearchModalStyles(); + const home = navItems.take(HOME_ID); + const catalog = navItems.take(CATALOG_ID); + const platform = navItems.take(PLATFORM_ID); + const costInsights = navItems.take(COST_INSIGHTS_ID); + const apis = navItems.take(APIS_ID); + const create = navItems.take(CREATE_ID); + + return ( + + + +
+ } to="/search"> + + + + {({ toggleModal }) => ( + + )} + + + +
+
+ + }> + {home && } + {catalog && } + {platform && } + {costInsights && } + + {apis && } + {create && } + + + + + } + to="/settings" + > + + + + +
+ ); +} diff --git a/packages/portal-app/src/components/Root/Root.tsx b/packages/portal-app/src/components/Root/Root.tsx deleted file mode 100644 index 55261443a..000000000 --- a/packages/portal-app/src/components/Root/Root.tsx +++ /dev/null @@ -1,282 +0,0 @@ -import { PropsWithChildren, useEffect } from 'react'; -import { makeStyles, Tooltip } from '@material-ui/core'; -import HomeIcon from '@material-ui/icons/Home'; -import ExtensionIcon from '@material-ui/icons/Extension'; -import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; -import ExitToAppIcon from '@material-ui/icons/ExitToApp'; -import LogoFull from './LogoFull'; -import LogoIcon from './LogoIcon'; -import { - Settings as SidebarSettings, - UserSettingsSignInAvatar, -} from '@backstage/plugin-user-settings'; -import { - SidebarSearchModal, - SearchModalProvider, - useSearchModal, -} from '@backstage/plugin-search'; -import { CustomSearchModal } from '../search/CustomSearchModal'; -import { - Sidebar, - sidebarConfig, - SidebarDivider, - SidebarGroup, - SidebarItem, - SidebarPage, - SidebarScrollWrapper, - SidebarSpace, - useSidebarOpenState, - Link, -} from '@backstage/core-components'; -import MenuIcon from '@material-ui/icons/Menu'; -import { IconButton } from '@material-ui/core'; -import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; -import ChevronRightIcon from '@material-ui/icons/ChevronRight'; -import SearchIcon from '@material-ui/icons/Search'; -import { MyGroupsSidebarItem } from '@backstage/plugin-org'; -import GroupIcon from '@material-ui/icons/People'; -import { identityApiRef, useApi } from '@backstage/core-plugin-api'; -import CategoryIcon from '@material-ui/icons/Category'; -import BubbleChartIcon from '@material-ui/icons/BubbleChart'; -import MonetizationOnIcon from '@material-ui/icons/MonetizationOn'; -import { AssistantDrawerProvider } from '@openchoreo/backstage-plugin-openchoreo-portal-assistant'; -// This app composes some OpenChoreo entity tabs itself via legacy -// `EntityLayout.Route` JSX (see EntityPage.tsx), so they render OUTSIDE the -// plugins' `PluginWrapperBlueprint` scope. Mount `OpenChoreoQueryProvider` here -// (QueryClientProvider + the user-scoping context) so those host-composed tabs -// still get a QueryClient and per-user cache-key namespacing. It shares the same -// `queryClient` singleton the plugin wrappers use, so there is one cache; the -// SignOutButton clears that exact client on sign-out. -import { - OpenChoreoQueryProvider, - queryClient, -} from '@openchoreo/backstage-plugin-react'; -import { ScaffolderPreselectionProvider } from '../../scaffolder/ScaffolderPreselectionContext'; -import { DependencyGraphZoomOverrides } from '../graph/DependencyGraphZoomOverrides'; - -const isMac = - typeof navigator !== 'undefined' && - /Mac|iPhone|iPad/.test(navigator.userAgent); -const searchShortcutLabel = `Search (${isMac ? '⌘K' : 'Ctrl+K'})`; - -const useSearchModalStyles = makeStyles({ - '@global': { - // Override the search modal Dialog max-width from lg to md - '.MuiDialog-root[aria-label="Search Modal"] .MuiDialog-paperWidthLg': { - maxWidth: 960, - }, - }, -}); - -const useA11yStyles = makeStyles(theme => ({ - skipLink: { - position: 'absolute', - left: -9999, - top: 'auto', - width: 1, - height: 1, - overflow: 'hidden', - zIndex: theme.zIndex.tooltip + 1, - '&:focus': { - left: theme.spacing(2), - top: theme.spacing(2), - width: 'auto', - height: 'auto', - padding: theme.spacing(1, 2), - backgroundColor: theme.palette.background.paper, - color: theme.palette.text.primary, - border: `2px solid ${theme.palette.primary.main}`, - borderRadius: 4, - textDecoration: 'none', - }, - }, - mainContent: { - display: 'contents', - }, -})); - -const useSidebarLogoStyles = makeStyles(theme => ({ - root: { - width: sidebarConfig.drawerWidthClosed, - height: 2.25 * sidebarConfig.logoHeight, - display: 'flex', - flexFlow: 'row nowrap', - alignItems: 'center', - position: 'relative', - }, - link: { - width: sidebarConfig.drawerWidthClosed, - marginLeft: 24, - }, - toggleButton: { - display: 'none', - position: 'absolute', - right: -20, - top: '50%', - transform: 'translateY(-50%)', - backgroundColor: theme.palette.background.paper, - border: `1px solid ${theme.palette.divider}`, - boxShadow: theme.shadows[2], - zIndex: 1000, - '&:hover': { - backgroundColor: theme.palette.action.hover, - }, - }, -})); - -const SidebarLogo = () => { - const classes = useSidebarLogoStyles(); - const { isOpen, setOpen } = useSidebarOpenState(); - - const handleToggle = () => { - setOpen(!isOpen); - }; - - return ( -
- - {isOpen ? : } - - - {isOpen ? : } - -
- ); -}; - -const KeyboardShortcutSearchToggler = () => { - const { setOpen } = useSearchModal(); - useEffect(() => { - const handler = (event: KeyboardEvent) => { - if ((event.metaKey || event.ctrlKey) && event.key === 'k') { - event.preventDefault(); - setOpen(true); - } - }; - window.addEventListener('keydown', handler); - return () => window.removeEventListener('keydown', handler); - }, [setOpen]); - return null; -}; - -const SignOutButton = () => { - const identityApi = useApi(identityApiRef); - - const handleSignOut = async () => { - await identityApi.signOut(); - // Drop every cached BFF response so the next user can't see the previous - // user's permission-scoped data. The reload below already clears in-memory - // cache, but clearing explicitly keeps this correct if the reload is ever - // removed and is the seam a future persisted cache would hook into. - queryClient.clear(); - // Reload to clear session and redirect to sign-in page - window.location.href = '/'; - }; - - return ( - - ); -}; - -export const Root = ({ children }: PropsWithChildren<{}>) => { - useSearchModalStyles(); - const a11yClasses = useA11yStyles(); - return ( - - - - {/* - Mounted inside (which lives under per - convertLegacyAppRoot's children-recognition rules) so the - component's MutationObserver runs in the routed subtree. The - previous placement as an sibling was silently - dropped by convertLegacyAppRoot during the NFS migration. - */} - - - Skip to main content - - - - - -
- } - to="/search" - > - - - - {({ toggleModal }) => ( - - )} - - - -
-
- - }> - {/* Global nav, not org-specific */} - - - - - - - {/* TechDocs disabled until proper production support is implemented */} - {/* */} - - {/* End global nav */} - - {/* Items in this group will be scrollable if they run out of space */} - - - - - } - to="/settings" - > - - - - -
-
- {children} -
-
-
-
-
- ); -}; diff --git a/packages/portal-app/src/components/Root/index.ts b/packages/portal-app/src/components/Root/index.ts deleted file mode 100644 index 35288815f..000000000 --- a/packages/portal-app/src/components/Root/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { Root } from './Root'; diff --git a/packages/portal-app/src/components/apiOverview/ApiOpenApiDefinitionTab.tsx b/packages/portal-app/src/components/apiOverview/ApiOpenApiDefinitionTab.tsx new file mode 100644 index 000000000..46657751e --- /dev/null +++ b/packages/portal-app/src/components/apiOverview/ApiOpenApiDefinitionTab.tsx @@ -0,0 +1,48 @@ +import { useEntity } from '@backstage/plugin-catalog-react'; +import Box from '@material-ui/core/Box'; +import Typography from '@material-ui/core/Typography'; +import { makeStyles } from '@material-ui/core/styles'; +import { YamlEditor } from '@openchoreo/backstage-plugin-react'; +import type { ApiEntity } from '@backstage/catalog-model'; + +// Mirrors ResourceDefinitionTab styling. Read-only OpenAPI YAML from spec. +const useStyles = makeStyles(theme => ({ + container: { + height: '100%', + display: 'flex', + flexDirection: 'column', + }, + header: { + marginBottom: theme.spacing(2), + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + }, + editorContainer: { + flex: 1, + minHeight: 500, + border: `1px solid ${theme.palette.divider}`, + borderRadius: theme.shape.borderRadius, + overflow: 'hidden', + }, +})); + +export function ApiOpenApiDefinitionTab() { + const classes = useStyles(); + const { entity } = useEntity(); + const definition = + typeof entity.spec?.definition === 'string' ? entity.spec.definition : ''; + + return ( + + + + API Definition: {entity.metadata.name} + + + + {}} readOnly /> + + + ); +} diff --git a/packages/portal-app/src/components/apiOverview/ApiOverviewLayout.tsx b/packages/portal-app/src/components/apiOverview/ApiOverviewLayout.tsx new file mode 100644 index 000000000..a4b233799 --- /dev/null +++ b/packages/portal-app/src/components/apiOverview/ApiOverviewLayout.tsx @@ -0,0 +1,48 @@ +import Grid from '@material-ui/core/Grid'; +import { makeStyles } from '@material-ui/core/styles'; +import type { EntityContentLayoutProps } from '@backstage/plugin-catalog-react/alpha'; +import { + ProvidingComponentsCard, + ConsumingComponentsCard, +} from '@backstage/plugin-api-docs'; +import { + ContainedCatalogGraphCard, + EntityWarningStrip, + ForeignCardsSection, + OpenChoreoAboutCard, +} from '@openchoreo/backstage-plugin'; + +// Normalize title size across BUI cards (h3.bui-Text) and MUI cards +// (MuiCardHeader-title-* — hashed in this app, matched via [class*=...]). +const useStyles = makeStyles(theme => ({ + root: { + '& [class*="MuiCardHeader-title"], & h3.bui-Text': { + fontSize: '1rem', + fontWeight: 600, + lineHeight: 1.6, + color: theme.palette.text.primary, + }, + }, +})); + +export default function ApiOverviewLayout({ cards }: EntityContentLayoutProps) { + const classes = useStyles(); + return ( + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/portal-app/src/components/catalog/CustomCatalogImportPage.tsx b/packages/portal-app/src/components/catalog/CustomCatalogImportPage.tsx index 538ff5d92..e6261599c 100644 --- a/packages/portal-app/src/components/catalog/CustomCatalogImportPage.tsx +++ b/packages/portal-app/src/components/catalog/CustomCatalogImportPage.tsx @@ -6,16 +6,11 @@ import { Page, SupportButton, } from '@backstage/core-components'; -import { - useApi, - configApiRef, - createRoutableExtension, -} from '@backstage/core-plugin-api'; +import { useApi, configApiRef } from '@backstage/core-plugin-api'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { ImportInfoCard, ImportStepper, - catalogImportPlugin, } from '@backstage/plugin-catalog-import'; import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import/alpha'; import Box from '@material-ui/core/Box'; @@ -40,7 +35,7 @@ const useStyles = makeStyles({ }, }); -const CustomCatalogImportPageContent = () => { +export const CustomCatalogImportPage = () => { const classes = useStyles(); const navigate = useNavigate(); const theme = useTheme(); @@ -93,13 +88,3 @@ const CustomCatalogImportPageContent = () => { ); }; - -// Routable extension on the catalog-import plugin so the `importPage` routeRef -// keeps its path for useRouteRef consumers and bound external routes. -export const CustomCatalogImportPage = catalogImportPlugin.provide( - createRoutableExtension({ - name: 'CustomCatalogImportPage', - component: () => Promise.resolve(CustomCatalogImportPageContent), - mountPoint: catalogImportPlugin.routes.importPage, - }), -); diff --git a/packages/portal-app/src/components/scaffolder/OpenChoreoScaffolderPage.tsx b/packages/portal-app/src/components/scaffolder/OpenChoreoScaffolderPage.tsx index 6fd32b3e1..728f7a239 100644 --- a/packages/portal-app/src/components/scaffolder/OpenChoreoScaffolderPage.tsx +++ b/packages/portal-app/src/components/scaffolder/OpenChoreoScaffolderPage.tsx @@ -1,51 +1,25 @@ +import useAsync from 'react-use/esm/useAsync'; import { ScaffolderPage } from '@backstage/plugin-scaffolder'; -import { ScaffolderFieldExtensions } from '@backstage/plugin-scaffolder-react'; +import { formFieldsApiRef } from '@backstage/plugin-scaffolder-react/alpha'; +import { useApi } from '@backstage/core-plugin-api'; +import { Progress, ResponseErrorPanel } from '@backstage/core-components'; import { ScaffolderLayout } from '../../scaffolder/ScaffolderLayout'; -import { ComponentNamePickerFieldExtension } from '../../scaffolder/ComponentNamePicker'; -import { ResourceNamePickerFieldExtension } from '../../scaffolder/ResourceNamePicker'; -import { BuildTemplatePickerFieldExtension } from '../../scaffolder/BuildTemplatePicker'; -import { BuildTemplateParametersFieldExtension } from '../../scaffolder/BuildTemplateParameters'; -import { BuildWorkflowPickerFieldExtension } from '../../scaffolder/BuildWorkflowPicker'; -import { BuildWorkflowParametersFieldExtension } from '../../scaffolder/BuildWorkflowParameters'; -import { TraitsFieldExtension } from '../../scaffolder/TraitsField'; -import { SwitchFieldExtension } from '../../scaffolder/SwitchField'; -import { AdvancedConfigurationFieldExtension } from '../../scaffolder/AdvancedConfigurationField'; -import { DeploymentSourcePickerFieldExtension } from '../../scaffolder/DeploymentSourcePicker'; -import { BuildAndDeployFieldExtension } from '../../scaffolder/BuildAndDeployField'; -import { ContainerImageFieldExtension } from '../../scaffolder/ContainerImageField'; -import { ComponentTypeYamlEditorFieldExtension } from '../../scaffolder/ComponentTypeYamlEditor'; -import { TraitYamlEditorFieldExtension } from '../../scaffolder/TraitYamlEditor'; -import { ClusterComponentTypeYamlEditorFieldExtension } from '../../scaffolder/ClusterComponentTypeYamlEditor'; -import { ClusterResourceTypeYamlEditorFieldExtension } from '../../scaffolder/ClusterResourceTypeYamlEditor'; -import { ResourceTypeYamlEditorFieldExtension } from '../../scaffolder/ResourceTypeYamlEditor'; -import { ClusterProjectTypeYamlEditorFieldExtension } from '../../scaffolder/ClusterProjectTypeYamlEditor'; -import { ProjectTypeYamlEditorFieldExtension } from '../../scaffolder/ProjectTypeYamlEditor'; -import { ResourceParametersFieldExtension } from '../../scaffolder/ResourceParametersField'; -import { ProjectParametersFieldExtension } from '../../scaffolder/ProjectParametersField'; -import { ClusterTraitYamlEditorFieldExtension } from '../../scaffolder/ClusterTraitYamlEditor'; -import { ComponentWorkflowYamlEditorFieldExtension } from '../../scaffolder/ComponentWorkflowYamlEditor'; -import { ClusterWorkflowYamlEditorFieldExtension } from '../../scaffolder/ClusterWorkflowYamlEditor'; -import { GitSourceFieldExtension } from '../../scaffolder/GitSourceField'; -import { ProjectNamespaceFieldExtension } from '../../scaffolder/ProjectNamespaceField'; -import { NamespaceEntityPickerFieldExtension } from '../../scaffolder/NamespaceEntityPicker'; -import { DeploymentPipelinePickerFieldExtension } from '../../scaffolder/DeploymentPipelinePicker'; -import { EnvironmentFormWithYamlFieldExtension } from '../../scaffolder/EnvironmentFormWithYaml'; -import { NotificationChannelFormWithYamlFieldExtension } from '../../scaffolder/NotificationChannelFormWithYaml'; -import { DeploymentPipelineFormWithYamlFieldExtension } from '../../scaffolder/DeploymentPipelineFormWithYaml'; -import { WorkloadDetailsFieldExtension } from '../../scaffolder/WorkloadDetailsField'; import { CustomTemplateListPage } from './CustomTemplateListPage'; import { OpenChoreoTemplateOutputs } from './OpenChoreoTemplateOutputs'; import { CustomReviewStep } from '../../scaffolder/CustomReviewState'; -/** - * Host's scaffolder page composition — `` with the - * OpenChoreo header copy, the CustomTemplateListPage / CustomReviewStep - * component overrides, and all 27 field-extension children. Used by the - * `page:scaffolder` override in customOverrides.tsx so /create renders - * through NFS without losing any of the host customizations the legacy - * `...}>` mount supplied. - */ +// Loads FormFieldBlueprint contributions (registered by +// @openchoreo/backstage-plugin) via formFieldsApiRef and passes them as the +// `formFields` prop instead of the pre-NFS JSX-children pattern. export function OpenChoreoScaffolderPage() { + const formFieldsApi = useApi(formFieldsApiRef); + const { + value: formFields, + loading, + error, + } = useAsync(() => formFieldsApi.loadFormFields(), [formFieldsApi]); + if (loading) return ; + if (error) return ; return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // formFields is accepted by the internal Router but omitted from + // RouterProps' TS declaration; runtime handles it fine. + {...({ formFields } as any)} + /> ); } diff --git a/packages/portal-app/src/components/settings/OpenChoreoUserSettingsPage.tsx b/packages/portal-app/src/components/settings/OpenChoreoUserSettingsPage.tsx new file mode 100644 index 000000000..1d5028a78 --- /dev/null +++ b/packages/portal-app/src/components/settings/OpenChoreoUserSettingsPage.tsx @@ -0,0 +1,41 @@ +import Grid from '@material-ui/core/Grid'; +import { + SettingsLayout, + UserSettingsProfileCard, + UserSettingsAppearanceCard, + UserSettingsIdentityCard, +} from '@backstage/plugin-user-settings'; +import { + AccessControlContent, + PlatformAboutCard, + SecretsContent, +} from '@openchoreo/backstage-plugin'; + +// Curated three-tab /settings page. Skips upstream's SubPageBlueprint +// collection (Auth Providers / Feature Flags don't appear). +export const OpenChoreoUserSettingsPage = () => ( + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/packages/portal-app/src/createPortalApp.tsx b/packages/portal-app/src/createPortalApp.tsx index e738511b6..b49f90d1c 100644 --- a/packages/portal-app/src/createPortalApp.tsx +++ b/packages/portal-app/src/createPortalApp.tsx @@ -1,40 +1,16 @@ -import { Route } from 'react-router-dom'; -import Grid from '@material-ui/core/Grid'; import { catalogPlugin } from '@backstage/plugin-catalog'; import { catalogImportPlugin } from '@backstage/plugin-catalog-import'; import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { SearchPage } from '@backstage/plugin-search'; -import { - TechDocsIndexPage, - techdocsPlugin, - TechDocsReaderPage, -} from '@backstage/plugin-techdocs'; -import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; -import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; -import { apis } from './apis'; -import { CustomApiExplorerPage } from './components/catalog/CustomApiExplorerPage'; -import { CustomCatalogImportPage } from './components/catalog/CustomCatalogImportPage'; -import { searchPage } from './components/search/SearchPage'; -import { Root } from './components/Root'; -import { HomePage } from './components/Home'; -import { CustomGraphNode } from '@openchoreo/backstage-plugin-react'; +import { techdocsPlugin } from '@backstage/plugin-techdocs'; +import { techDocsReportIssueAddonModule } from '@backstage/plugin-techdocs-module-addons-contrib/alpha'; import { PageLoader } from '@openchoreo/backstage-design-system'; -import { PlatformOverviewPage } from './components/platformOverview'; -import { CostInsightsPage } from '@openchoreo/backstage-plugin-openchoreo-observability'; -import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; import { createApp } from '@backstage/frontend-defaults'; import type { FrontendFeature } from '@backstage/frontend-plugin-api'; -import { - convertLegacyAppOptions, - convertLegacyAppRoot, -} from '@backstage/core-compat-api'; -import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; +import { appModule } from './appModule'; -// NFS plugin features (created in Step 2 — each plugin's `/alpha` exports a -// `createFrontendPlugin` instance). These replace the API factory entries -// that previously lived in `apis.ts`. import openchoreoPluginAlpha, { + openChoreoAppModule, openChoreoEntityPageOverride, } from '@openchoreo/backstage-plugin/alpha'; import openchoreoCiPluginAlpha from '@openchoreo/backstage-plugin-openchoreo-ci/alpha'; @@ -42,167 +18,23 @@ import openchoreoObservabilityPluginAlpha from '@openchoreo/backstage-plugin-ope import openchoreoWorkflowsPluginAlpha from '@openchoreo/backstage-plugin-openchoreo-workflows/alpha'; import platformEngineerCorePluginAlpha from '@openchoreo/backstage-plugin-platform-engineer-core/alpha'; -// Upstream NFS plugin features with our overrides: -// - catalog graph default API replaced to include OpenChoreo custom relations -// - catalog entity-presentation default API replaced to add custom kind icons -// - scaffolder `page:scaffolder` disabled (our legacy wins) -// and form-decorators API replaced to inject the openChoreoTokenDecorator -// - customTranslationsModule reinstates the catalog-import header overrides -// that previously rode via createApp.__experimentalTranslations import { apiDocsPluginAlpha, catalogGraphPluginAlpha, + catalogImportPluginAlpha, catalogPluginAlpha, customAppModule, scaffolderPluginAlpha as upstreamScaffolderPluginAlpha, + searchPluginAlpha, techdocsPluginAlpha, + userSettingsPluginAlpha, } from './apis/customOverrides'; -// catalog-import NFS plugin — registered so the `/catalog-import` route ref -// resolves under NFS. Our legacy `` -// mount in `` provides the actual page rendering. -import catalogImportPluginAlpha from '@backstage/plugin-catalog-import/alpha'; -// api-docs and kubernetes NFS plugins — registered so that `apiDocsConfigRef`, -// `kubernetesApiRef`, etc. are present in the api holder. The host owns the -// `/api-docs` route (CustomApiExplorerPage) and the Kubernetes entity tab -// reuses upstream `EntityKubernetesContent`; without these features the apis -// they depend on are absent and the tabs throw `NotImplementedError`. import kubernetesPluginAlpha from '@backstage/plugin-kubernetes/alpha'; -// Community CI plugins — each ships an `/alpha` that contributes their -// entity-page tabs via `EntityContentBlueprint`. Registered so the Jenkins / -// GitHub Actions / GitLab tabs continue to show up on Component pages once -// the hand-authored `EntityPage.tsx` is gone. Each plugin filters itself by -// the relevant annotation. -// -// `apiDocsPluginAlpha` and `techdocsPluginAlpha` come from -// `./apis/customOverrides` instead of the raw upstream alphas — they extend -// the default filters with `hasApis(entity)` and `isTechDocsAvailable(entity)` -// so the "APIs" and "TechDocs" tabs are hidden when the entity doesn't -// support them (the pre-NFS `EntityPage.tsx` predicates, ported). import jenkinsPluginAlpha from '@backstage-community/plugin-jenkins/alpha'; import githubActionsPluginAlpha from '@backstage-community/plugin-github-actions/alpha'; import gitlabPluginAlpha from '@immobiliarelabs/backstage-plugin-gitlab/alpha'; -import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; -import { RequirePermission } from '@backstage/plugin-permission-react'; -import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; -import { appThemes } from './themes'; -import { LEGACY_KIND_ICONS } from './kindIcons'; -import { - AccessControlContent, - PlatformAboutCard, - SecretsContent, - ExecTerminalWindowPage, -} from '@openchoreo/backstage-plugin'; -import { - UserSettingsPage, - SettingsLayout, - UserSettingsProfileCard, - UserSettingsAppearanceCard, - UserSettingsIdentityCard, -} from '@backstage/plugin-user-settings'; -import { VisitListener } from '@backstage/plugin-home'; - -const legacyAppOptions = convertLegacyAppOptions({ - apis, - icons: LEGACY_KIND_ICONS, - themes: appThemes, -}); - -const routes = ( - - } /> - {/* - `/catalog` is owned by the NFS `page:catalog` extension and - `/catalog/:namespace/:kind/:name` by `page:catalog/entity` — see - customOverrides.tsx, which overrides each loader to render the - host's `` and the legacy `entityPage` JSX - respectively. The legacy `` mount used to - live here but double-rendered the catalog header under the NFS - compat shim. - */} - } /> - } - > - - - - - {/* - `/create` is owned by the NFS `page:scaffolder` extension — see - customOverrides.tsx, which overrides its loader to render - `` (the host's `` with - the 27 field-extension children and `CustomTemplateListPage` / - `CustomReviewStep` components). The legacy `` - mount used to live here but double-rendered the scaffolder header - under the NFS compat shim. - */} - } /> - - - - } - /> - }> - {searchPage} - - }> - - - {/* The stock grid, recomposed to append the - OpenChoreo platform version card (openchoreo/openchoreo#4344). */} - - - - - - - - - - - - - - - - - - - - - - - - } - /> - } /> - } /> - {/* - Standalone full-window exec terminal, opened in a new browser tab from the - resource drawer. The page renders a fixed viewport overlay over the app - chrome, so the terminal uses the entire window. - */} - } /> - -); - -const legacyRoot = convertLegacyAppRoot( - <> - - - - - {routes} - - , -); +import { portalAppPlugin } from './portalPlugin'; /** Options for {@link createPortalApp}. */ export interface PortalAppOptions { @@ -220,16 +52,11 @@ export interface PortalAppOptions { export function createPortalApp(options?: PortalAppOptions) { return createApp({ features: [ - // `...legacyRoot` re-emits each legacy plugin's `apis: [...]` array as - // ApiBlueprint extensions under the legacy plugin's own pluginId - // (collectLegacyRoutes). The NFS api-factory registry resolves - // same-pluginId factories last-write-wins, so the override features - // below MUST come after `...legacyRoot` to win the contest. Otherwise - // our custom catalog-graph relations, entity-presentation kind icons, - // and scaffolder form-decorator override get silently overwritten by - // upstream defaults at startup. - legacyAppOptions, - ...legacyRoot, + // APIs first so overrides below win via last-write-wins. + appModule, + // Ships OC fetch/permission overrides on pluginId 'app' so they + // replace Backstage's default core.fetch / plugin.permission.api. + openChoreoAppModule, customAppModule, upstreamScaffolderPluginAlpha, catalogGraphPluginAlpha, @@ -238,24 +65,21 @@ export function createPortalApp(options?: PortalAppOptions) { apiDocsPluginAlpha, kubernetesPluginAlpha, techdocsPluginAlpha, + searchPluginAlpha, jenkinsPluginAlpha, githubActionsPluginAlpha, gitlabPluginAlpha, - // `openchoreoCiPluginAlpha` is registered BEFORE `openchoreoPluginAlpha` - // so that within the `deployment` tab group the "Build" tab (contributed - // by CI) shows before "Deploy" (contributed by the base plugin) — matching - // the pre-NFS `EntityPage.tsx` order. Tab ordering by group is handled by - // `openChoreoEntityPageOverride`'s sort; ties within a group fall back to - // registration order, which this line controls. + userSettingsPluginAlpha, + techDocsReportIssueAddonModule, + // `openchoreoCiPluginAlpha` before `openchoreoPluginAlpha` so within + // the `deployment` tab group Build (CI) shows before Deploy (base). openchoreoCiPluginAlpha, openchoreoPluginAlpha, openchoreoObservabilityPluginAlpha, openchoreoWorkflowsPluginAlpha, platformEngineerCorePluginAlpha, - // Overrides the canonical `page:catalog/entity` loader to mount - // `OpenChoreoEntityLayout` (compact header + styled tab bar + delete / - // annotation menu items). Ships from `@openchoreo/backstage-plugin/alpha` - // so external adopters can opt in the same way. + portalAppPlugin, + // Mounts OpenChoreoEntityLayout as the page:catalog/entity chrome. openChoreoEntityPageOverride, ...(options?.features ?? []), ], @@ -271,8 +95,6 @@ export function createPortalApp(options?: PortalAppOptions) { }); }, advanced: { - // App-boot loader (waiting for config/features) — use our centered loader - // instead of the default Backstage progress bar. loadingElement: , }, }); diff --git a/packages/portal-app/src/kindIcons.ts b/packages/portal-app/src/kindIcons.ts index a14f15b92..7081b4b3f 100644 --- a/packages/portal-app/src/kindIcons.ts +++ b/packages/portal-app/src/kindIcons.ts @@ -12,12 +12,10 @@ import ExtensionIcon from '@material-ui/icons/Extension'; import PlayCircleOutlineIcon from '@material-ui/icons/PlayCircleOutline'; import SettingsApplicationsIcon from '@material-ui/icons/SettingsApplications'; -/** - * Single source of truth for OpenChoreo platform kind icons. Both - * `App.tsx` (legacy `convertLegacyAppOptions.icons` shape, `kind:`) - * and `customOverrides.tsx` (NFS `DefaultEntityPresentationApi.kindIcons` - * shape, bare ``) derive their respective keyed shapes from this map. - */ +// Single source of truth for OpenChoreo platform kind icons. Consumed +// twice: `IconBundleBlueprint` (kind:x-keyed shape via LEGACY_KIND_ICONS) +// and `DefaultEntityPresentationApi.kindIcons` (bare-x shape). + export const KIND_ICONS: Record = { environment: CloudIcon, observabilityalertsnotificationchannel: NotificationsIcon, @@ -42,7 +40,7 @@ export const KIND_ICONS: Record = { componentworkflow: SettingsApplicationsIcon, }; -/** `kind:`-keyed shape consumed by `convertLegacyAppOptions.icons`. */ +/** kind:x-keyed shape consumed by IconBundleBlueprint (see portalPlugin.tsx). */ export const LEGACY_KIND_ICONS = Object.fromEntries( Object.entries(KIND_ICONS).map(([k, v]) => [`kind:${k}`, v]), ); diff --git a/packages/portal-app/src/portalPlugin.tsx b/packages/portal-app/src/portalPlugin.tsx new file mode 100644 index 000000000..d96346b02 --- /dev/null +++ b/packages/portal-app/src/portalPlugin.tsx @@ -0,0 +1,64 @@ +import HomeIcon from '@material-ui/icons/Home'; +import { VisitListener } from '@backstage/plugin-home'; +import { + AppRootElementBlueprint, + createFrontendPlugin, + createRouteRef, + PageBlueprint, + PluginWrapperBlueprint, +} from '@backstage/frontend-plugin-api'; +import { DependencyGraphZoomOverrides } from './components/graph/DependencyGraphZoomOverrides'; + +export const homeRouteRef = createRouteRef(); + +// Gives portal-owned pages a QueryClientProvider for OC hooks. +const queryProvider = PluginWrapperBlueprint.make({ + name: 'query-provider', + params: defineParams => + defineParams({ + loader: async () => { + const { OpenChoreoQueryProvider } = await import( + '@openchoreo/backstage-plugin-react' + ); + return { component: OpenChoreoQueryProvider }; + }, + }), +}); + +// MutationObserver fix for d3-graph zoom; must run in the routed subtree. +const dependencyGraphZoomOverridesElement = AppRootElementBlueprint.make({ + name: 'dependency-graph-zoom-overrides', + params: { element: }, +}); + +// Upstream ships its own copy disabled; we contribute one. +const visitListenerElement = AppRootElementBlueprint.make({ + name: 'visit-listener', + params: { element: }, +}); + +const homePage = PageBlueprint.make({ + name: 'home', + params: { + path: '/', + routeRef: homeRouteRef, + title: 'Home', + icon: , + // Page renders its own
; suppress outer PageLayout header. + noHeader: true, + loader: () => import('./components/Home').then(m => ), + }, +}); + +// Attachments to non-internal inputs (routes, root elements) live here. +// Internal-input attachments (themes/icons/nav-content/root-wrappers) live +// in `appModule.tsx` under pluginId 'app'. +export const portalAppPlugin = createFrontendPlugin({ + pluginId: 'openchoreo-portal-app', + extensions: [ + queryProvider, + dependencyGraphZoomOverridesElement, + visitListenerElement, + homePage, + ], +}); diff --git a/packages/portal-app/src/scaffolder/AdvancedConfigurationField/extensions.ts b/packages/portal-app/src/scaffolder/AdvancedConfigurationField/extensions.ts deleted file mode 100644 index c34cc74b5..000000000 --- a/packages/portal-app/src/scaffolder/AdvancedConfigurationField/extensions.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - AdvancedConfigurationField, - AdvancedConfigurationFieldSchema, - advancedConfigurationFieldValidation, -} from './AdvancedConfigurationField'; - -export const AdvancedConfigurationFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'AdvancedConfigurationField', - component: AdvancedConfigurationField, - schema: AdvancedConfigurationFieldSchema, - validation: advancedConfigurationFieldValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/AdvancedConfigurationField/index.ts b/packages/portal-app/src/scaffolder/AdvancedConfigurationField/index.ts deleted file mode 100644 index a5a301dfa..000000000 --- a/packages/portal-app/src/scaffolder/AdvancedConfigurationField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { AdvancedConfigurationFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/BuildAndDeployField/extensions.ts b/packages/portal-app/src/scaffolder/BuildAndDeployField/extensions.ts deleted file mode 100644 index cd4dd823f..000000000 --- a/packages/portal-app/src/scaffolder/BuildAndDeployField/extensions.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - BuildAndDeployField, - BuildAndDeployFieldSchema, -} from './BuildAndDeployField'; - -export const BuildAndDeployFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'BuildAndDeployField', - component: BuildAndDeployField, - schema: BuildAndDeployFieldSchema, - }), -); diff --git a/packages/portal-app/src/scaffolder/BuildAndDeployField/index.ts b/packages/portal-app/src/scaffolder/BuildAndDeployField/index.ts deleted file mode 100644 index 144a5ccd2..000000000 --- a/packages/portal-app/src/scaffolder/BuildAndDeployField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { BuildAndDeployFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/BuildTemplateParameters/extensions.ts b/packages/portal-app/src/scaffolder/BuildTemplateParameters/extensions.ts deleted file mode 100644 index c51e91fb8..000000000 --- a/packages/portal-app/src/scaffolder/BuildTemplateParameters/extensions.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - BuildTemplateParameters, - buildTemplateParametersValidation, -} from './BuildTemplateParametersExtension'; - -export const BuildTemplateParametersFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'BuildTemplateParameters', - component: BuildTemplateParameters, - validation: buildTemplateParametersValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/BuildTemplateParameters/index.ts b/packages/portal-app/src/scaffolder/BuildTemplateParameters/index.ts deleted file mode 100644 index 74a07bede..000000000 --- a/packages/portal-app/src/scaffolder/BuildTemplateParameters/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { - BuildTemplateParameters, - BuildTemplateParametersSchema, - buildTemplateParametersValidation, -} from './BuildTemplateParametersExtension'; -export { BuildTemplateParametersFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/BuildTemplatePicker/extensions.ts b/packages/portal-app/src/scaffolder/BuildTemplatePicker/extensions.ts deleted file mode 100644 index 067173f19..000000000 --- a/packages/portal-app/src/scaffolder/BuildTemplatePicker/extensions.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - This is where the magic happens and creates the custom field extension. -*/ - -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - BuildTemplatePicker, - buildTemplatePickerValidation, - // BuildTemplatePickerSchema, -} from './BuildTemplatePickerExtension'; - -export const BuildTemplatePickerFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'BuildTemplatePicker', - component: BuildTemplatePicker, - validation: buildTemplatePickerValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/BuildTemplatePicker/index.ts b/packages/portal-app/src/scaffolder/BuildTemplatePicker/index.ts deleted file mode 100644 index 7df9e1526..000000000 --- a/packages/portal-app/src/scaffolder/BuildTemplatePicker/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { BuildTemplatePickerFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/BuildWorkflowParameters/index.tsx b/packages/portal-app/src/scaffolder/BuildWorkflowParameters/index.tsx deleted file mode 100644 index b92a53fad..000000000 --- a/packages/portal-app/src/scaffolder/BuildWorkflowParameters/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - BuildWorkflowParameters, - BuildWorkflowParametersSchema, - buildWorkflowParametersValidation, -} from './BuildWorkflowParametersExtension'; - -export const BuildWorkflowParametersFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'BuildWorkflowParameters', - component: BuildWorkflowParameters, - schema: BuildWorkflowParametersSchema, - validation: buildWorkflowParametersValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/BuildWorkflowPicker/index.tsx b/packages/portal-app/src/scaffolder/BuildWorkflowPicker/index.tsx deleted file mode 100644 index ab8f0cf50..000000000 --- a/packages/portal-app/src/scaffolder/BuildWorkflowPicker/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - BuildWorkflowPicker, - BuildWorkflowPickerSchema, - buildWorkflowPickerValidation, -} from './BuildWorkflowPickerExtension'; - -export const BuildWorkflowPickerFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'BuildWorkflowPicker', - component: BuildWorkflowPicker, - schema: BuildWorkflowPickerSchema, - validation: buildWorkflowPickerValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/ClusterComponentTypeYamlEditor/extensions.ts b/packages/portal-app/src/scaffolder/ClusterComponentTypeYamlEditor/extensions.ts deleted file mode 100644 index 1553f61fb..000000000 --- a/packages/portal-app/src/scaffolder/ClusterComponentTypeYamlEditor/extensions.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - ClusterComponentTypeYamlEditorExtension, - clusterComponentTypeYamlEditorValidation, -} from './ClusterComponentTypeYamlEditorExtension'; - -export const ClusterComponentTypeYamlEditorFieldExtension = - scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ClusterComponentTypeYamlEditor', - component: ClusterComponentTypeYamlEditorExtension, - validation: clusterComponentTypeYamlEditorValidation, - }), - ); diff --git a/packages/portal-app/src/scaffolder/ClusterComponentTypeYamlEditor/index.ts b/packages/portal-app/src/scaffolder/ClusterComponentTypeYamlEditor/index.ts deleted file mode 100644 index 9e76da6aa..000000000 --- a/packages/portal-app/src/scaffolder/ClusterComponentTypeYamlEditor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ClusterComponentTypeYamlEditorFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/ClusterProjectTypeYamlEditor/extensions.ts b/packages/portal-app/src/scaffolder/ClusterProjectTypeYamlEditor/extensions.ts deleted file mode 100644 index 4194b35e1..000000000 --- a/packages/portal-app/src/scaffolder/ClusterProjectTypeYamlEditor/extensions.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - ClusterProjectTypeYamlEditorExtension, - clusterProjectTypeYamlEditorValidation, -} from './ClusterProjectTypeYamlEditorExtension'; - -export const ClusterProjectTypeYamlEditorFieldExtension = - scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ClusterProjectTypeYamlEditor', - component: ClusterProjectTypeYamlEditorExtension, - validation: clusterProjectTypeYamlEditorValidation, - }), - ); diff --git a/packages/portal-app/src/scaffolder/ClusterProjectTypeYamlEditor/index.ts b/packages/portal-app/src/scaffolder/ClusterProjectTypeYamlEditor/index.ts deleted file mode 100644 index ca3fa24e0..000000000 --- a/packages/portal-app/src/scaffolder/ClusterProjectTypeYamlEditor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ClusterProjectTypeYamlEditorFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/ClusterResourceTypeYamlEditor/extensions.ts b/packages/portal-app/src/scaffolder/ClusterResourceTypeYamlEditor/extensions.ts deleted file mode 100644 index 85b048e94..000000000 --- a/packages/portal-app/src/scaffolder/ClusterResourceTypeYamlEditor/extensions.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - ClusterResourceTypeYamlEditorExtension, - clusterResourceTypeYamlEditorValidation, -} from './ClusterResourceTypeYamlEditorExtension'; - -export const ClusterResourceTypeYamlEditorFieldExtension = - scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ClusterResourceTypeYamlEditor', - component: ClusterResourceTypeYamlEditorExtension, - validation: clusterResourceTypeYamlEditorValidation, - }), - ); diff --git a/packages/portal-app/src/scaffolder/ClusterResourceTypeYamlEditor/index.ts b/packages/portal-app/src/scaffolder/ClusterResourceTypeYamlEditor/index.ts deleted file mode 100644 index 23421afeb..000000000 --- a/packages/portal-app/src/scaffolder/ClusterResourceTypeYamlEditor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ClusterResourceTypeYamlEditorFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/ClusterTraitYamlEditor/extensions.ts b/packages/portal-app/src/scaffolder/ClusterTraitYamlEditor/extensions.ts deleted file mode 100644 index 61b2bae8d..000000000 --- a/packages/portal-app/src/scaffolder/ClusterTraitYamlEditor/extensions.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - ClusterTraitYamlEditorExtension, - clusterTraitYamlEditorValidation, -} from './ClusterTraitYamlEditorExtension'; - -export const ClusterTraitYamlEditorFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ClusterTraitYamlEditor', - component: ClusterTraitYamlEditorExtension, - validation: clusterTraitYamlEditorValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/ClusterTraitYamlEditor/index.ts b/packages/portal-app/src/scaffolder/ClusterTraitYamlEditor/index.ts deleted file mode 100644 index eeeb70076..000000000 --- a/packages/portal-app/src/scaffolder/ClusterTraitYamlEditor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ClusterTraitYamlEditorFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/ClusterWorkflowYamlEditor/extensions.ts b/packages/portal-app/src/scaffolder/ClusterWorkflowYamlEditor/extensions.ts deleted file mode 100644 index f5ac1be61..000000000 --- a/packages/portal-app/src/scaffolder/ClusterWorkflowYamlEditor/extensions.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - ClusterWorkflowYamlEditorExtension, - clusterWorkflowYamlEditorValidation, -} from './ClusterWorkflowYamlEditorExtension'; - -export const ClusterWorkflowYamlEditorFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ClusterWorkflowYamlEditor', - component: ClusterWorkflowYamlEditorExtension, - validation: clusterWorkflowYamlEditorValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/ClusterWorkflowYamlEditor/index.ts b/packages/portal-app/src/scaffolder/ClusterWorkflowYamlEditor/index.ts deleted file mode 100644 index 86e6ccdc3..000000000 --- a/packages/portal-app/src/scaffolder/ClusterWorkflowYamlEditor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ClusterWorkflowYamlEditorFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/ComponentNamePicker/extensions.ts b/packages/portal-app/src/scaffolder/ComponentNamePicker/extensions.ts deleted file mode 100644 index c9fbe85e4..000000000 --- a/packages/portal-app/src/scaffolder/ComponentNamePicker/extensions.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - This is where the magic happens and creates the custom field extension. -*/ - -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - ComponentNamePicker, - componentNamePickerValidation, -} from './ComponentNamePickerExtension'; - -export const ComponentNamePickerFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ComponentNamePicker', - component: ComponentNamePicker, - validation: componentNamePickerValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/ComponentNamePicker/index.ts b/packages/portal-app/src/scaffolder/ComponentNamePicker/index.ts deleted file mode 100644 index e2e1b89be..000000000 --- a/packages/portal-app/src/scaffolder/ComponentNamePicker/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ComponentNamePickerFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/ComponentTypeYamlEditor/extensions.ts b/packages/portal-app/src/scaffolder/ComponentTypeYamlEditor/extensions.ts deleted file mode 100644 index 658ebf978..000000000 --- a/packages/portal-app/src/scaffolder/ComponentTypeYamlEditor/extensions.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - ComponentTypeYamlEditorExtension, - componentTypeYamlEditorValidation, -} from './ComponentTypeYamlEditorExtension'; - -export const ComponentTypeYamlEditorFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ComponentTypeYamlEditor', - component: ComponentTypeYamlEditorExtension, - validation: componentTypeYamlEditorValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/ComponentTypeYamlEditor/index.ts b/packages/portal-app/src/scaffolder/ComponentTypeYamlEditor/index.ts deleted file mode 100644 index 0b47227d0..000000000 --- a/packages/portal-app/src/scaffolder/ComponentTypeYamlEditor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ComponentTypeYamlEditorFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/ComponentWorkflowYamlEditor/extensions.ts b/packages/portal-app/src/scaffolder/ComponentWorkflowYamlEditor/extensions.ts deleted file mode 100644 index c55bc8cdd..000000000 --- a/packages/portal-app/src/scaffolder/ComponentWorkflowYamlEditor/extensions.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - ComponentWorkflowYamlEditorExtension, - componentWorkflowYamlEditorValidation, -} from './ComponentWorkflowYamlEditorExtension'; - -export const ComponentWorkflowYamlEditorFieldExtension = - scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ComponentWorkflowYamlEditor', - component: ComponentWorkflowYamlEditorExtension, - validation: componentWorkflowYamlEditorValidation, - }), - ); diff --git a/packages/portal-app/src/scaffolder/ComponentWorkflowYamlEditor/index.ts b/packages/portal-app/src/scaffolder/ComponentWorkflowYamlEditor/index.ts deleted file mode 100644 index 385a1dc79..000000000 --- a/packages/portal-app/src/scaffolder/ComponentWorkflowYamlEditor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ComponentWorkflowYamlEditorFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/ContainerImageField/extensions.ts b/packages/portal-app/src/scaffolder/ContainerImageField/extensions.ts deleted file mode 100644 index 79d4916c9..000000000 --- a/packages/portal-app/src/scaffolder/ContainerImageField/extensions.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - ContainerImageField, - ContainerImageFieldSchema, - containerImageFieldValidation, -} from './ContainerImageField'; - -export const ContainerImageFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ContainerImageField', - component: ContainerImageField, - schema: ContainerImageFieldSchema, - validation: containerImageFieldValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/ContainerImageField/index.ts b/packages/portal-app/src/scaffolder/ContainerImageField/index.ts deleted file mode 100644 index e789d0398..000000000 --- a/packages/portal-app/src/scaffolder/ContainerImageField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ContainerImageFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/DeploymentPipelineFormWithYaml/extensions.ts b/packages/portal-app/src/scaffolder/DeploymentPipelineFormWithYaml/extensions.ts deleted file mode 100644 index 743eeaaa3..000000000 --- a/packages/portal-app/src/scaffolder/DeploymentPipelineFormWithYaml/extensions.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - DeploymentPipelineFormWithYamlExtension, - DeploymentPipelineFormWithYamlSchema, - deploymentPipelineFormWithYamlValidation, -} from './DeploymentPipelineFormWithYamlExtension'; - -export const DeploymentPipelineFormWithYamlFieldExtension = - scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'DeploymentPipelineFormWithYaml', - component: DeploymentPipelineFormWithYamlExtension, - schema: DeploymentPipelineFormWithYamlSchema, - validation: deploymentPipelineFormWithYamlValidation, - }), - ); diff --git a/packages/portal-app/src/scaffolder/DeploymentPipelineFormWithYaml/index.ts b/packages/portal-app/src/scaffolder/DeploymentPipelineFormWithYaml/index.ts deleted file mode 100644 index b2afd5cac..000000000 --- a/packages/portal-app/src/scaffolder/DeploymentPipelineFormWithYaml/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { DeploymentPipelineFormWithYamlFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/DeploymentPipelinePicker/extensions.ts b/packages/portal-app/src/scaffolder/DeploymentPipelinePicker/extensions.ts deleted file mode 100644 index a7f17e18a..000000000 --- a/packages/portal-app/src/scaffolder/DeploymentPipelinePicker/extensions.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { DeploymentPipelinePicker } from './DeploymentPipelinePickerExtension'; - -export const DeploymentPipelinePickerFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'DeploymentPipelinePicker', - component: DeploymentPipelinePicker, - }), -); diff --git a/packages/portal-app/src/scaffolder/DeploymentPipelinePicker/index.ts b/packages/portal-app/src/scaffolder/DeploymentPipelinePicker/index.ts deleted file mode 100644 index b74016d7c..000000000 --- a/packages/portal-app/src/scaffolder/DeploymentPipelinePicker/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { DeploymentPipelinePickerFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/DeploymentSourcePicker/extensions.ts b/packages/portal-app/src/scaffolder/DeploymentSourcePicker/extensions.ts deleted file mode 100644 index e541fc7e7..000000000 --- a/packages/portal-app/src/scaffolder/DeploymentSourcePicker/extensions.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - DeploymentSourcePicker, - DeploymentSourcePickerSchema, -} from './DeploymentSourcePicker'; - -export const DeploymentSourcePickerFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'DeploymentSourcePicker', - component: DeploymentSourcePicker, - schema: DeploymentSourcePickerSchema, - }), -); diff --git a/packages/portal-app/src/scaffolder/DeploymentSourcePicker/index.ts b/packages/portal-app/src/scaffolder/DeploymentSourcePicker/index.ts deleted file mode 100644 index 04dcd87af..000000000 --- a/packages/portal-app/src/scaffolder/DeploymentSourcePicker/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { DeploymentSourcePickerFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/EnvironmentFormWithYaml/extensions.ts b/packages/portal-app/src/scaffolder/EnvironmentFormWithYaml/extensions.ts deleted file mode 100644 index e0259079f..000000000 --- a/packages/portal-app/src/scaffolder/EnvironmentFormWithYaml/extensions.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - EnvironmentFormWithYamlExtension, - EnvironmentFormWithYamlSchema, - environmentFormWithYamlValidation, -} from './EnvironmentFormWithYamlExtension'; - -export const EnvironmentFormWithYamlFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'EnvironmentFormWithYaml', - component: EnvironmentFormWithYamlExtension, - schema: EnvironmentFormWithYamlSchema, - validation: environmentFormWithYamlValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/EnvironmentFormWithYaml/index.ts b/packages/portal-app/src/scaffolder/EnvironmentFormWithYaml/index.ts deleted file mode 100644 index 01c0f4c43..000000000 --- a/packages/portal-app/src/scaffolder/EnvironmentFormWithYaml/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { EnvironmentFormWithYamlFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/GitSourceField/extensions.ts b/packages/portal-app/src/scaffolder/GitSourceField/extensions.ts deleted file mode 100644 index cf6d497d6..000000000 --- a/packages/portal-app/src/scaffolder/GitSourceField/extensions.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - GitSourceField, - GitSourceFieldSchema, - gitSourceFieldValidation, -} from './GitSourceField'; - -export const GitSourceFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'GitSourceField', - component: GitSourceField, - schema: GitSourceFieldSchema, - validation: gitSourceFieldValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/GitSourceField/index.ts b/packages/portal-app/src/scaffolder/GitSourceField/index.ts deleted file mode 100644 index 732963549..000000000 --- a/packages/portal-app/src/scaffolder/GitSourceField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { GitSourceFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/NamespaceEntityPicker/extensions.ts b/packages/portal-app/src/scaffolder/NamespaceEntityPicker/extensions.ts deleted file mode 100644 index 8f9e7a328..000000000 --- a/packages/portal-app/src/scaffolder/NamespaceEntityPicker/extensions.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { NamespaceEntityPicker } from './NamespaceEntityPicker'; - -export const NamespaceEntityPickerFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'NamespaceEntityPicker', - component: NamespaceEntityPicker, - }), -); diff --git a/packages/portal-app/src/scaffolder/NotificationChannelFormWithYaml/extensions.ts b/packages/portal-app/src/scaffolder/NotificationChannelFormWithYaml/extensions.ts deleted file mode 100644 index 961686879..000000000 --- a/packages/portal-app/src/scaffolder/NotificationChannelFormWithYaml/extensions.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - NotificationChannelFormWithYamlExtension, - NotificationChannelFormWithYamlSchema, - notificationChannelFormWithYamlValidation, -} from './NotificationChannelFormWithYamlExtension'; - -export const NotificationChannelFormWithYamlFieldExtension = - scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'NotificationChannelFormWithYaml', - component: NotificationChannelFormWithYamlExtension, - schema: NotificationChannelFormWithYamlSchema, - validation: notificationChannelFormWithYamlValidation, - }), - ); diff --git a/packages/portal-app/src/scaffolder/NotificationChannelFormWithYaml/index.ts b/packages/portal-app/src/scaffolder/NotificationChannelFormWithYaml/index.ts deleted file mode 100644 index ad56199a0..000000000 --- a/packages/portal-app/src/scaffolder/NotificationChannelFormWithYaml/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { NotificationChannelFormWithYamlFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/ProjectNamespaceField/extensions.ts b/packages/portal-app/src/scaffolder/ProjectNamespaceField/extensions.ts deleted file mode 100644 index d074848d0..000000000 --- a/packages/portal-app/src/scaffolder/ProjectNamespaceField/extensions.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - ProjectNamespaceField, - ProjectNamespaceFieldSchema, - projectNamespaceFieldValidation, -} from './ProjectNamespaceField'; - -export const ProjectNamespaceFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ProjectNamespaceField', - component: ProjectNamespaceField, - schema: ProjectNamespaceFieldSchema, - validation: projectNamespaceFieldValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/ProjectNamespaceField/index.ts b/packages/portal-app/src/scaffolder/ProjectNamespaceField/index.ts deleted file mode 100644 index c3730412f..000000000 --- a/packages/portal-app/src/scaffolder/ProjectNamespaceField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ProjectNamespaceFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/ProjectParametersField/extensions.ts b/packages/portal-app/src/scaffolder/ProjectParametersField/extensions.ts deleted file mode 100644 index b2b64bd8c..000000000 --- a/packages/portal-app/src/scaffolder/ProjectParametersField/extensions.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - ProjectParametersField, - ProjectParametersFieldSchema, -} from './ProjectParametersField'; - -export const ProjectParametersFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ProjectParametersField', - component: ProjectParametersField, - schema: ProjectParametersFieldSchema, - }), -); diff --git a/packages/portal-app/src/scaffolder/ProjectParametersField/index.ts b/packages/portal-app/src/scaffolder/ProjectParametersField/index.ts deleted file mode 100644 index 4a5139c10..000000000 --- a/packages/portal-app/src/scaffolder/ProjectParametersField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ProjectParametersFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/ProjectTypeYamlEditor/extensions.ts b/packages/portal-app/src/scaffolder/ProjectTypeYamlEditor/extensions.ts deleted file mode 100644 index 50c8a741a..000000000 --- a/packages/portal-app/src/scaffolder/ProjectTypeYamlEditor/extensions.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - ProjectTypeYamlEditorExtension, - projectTypeYamlEditorValidation, -} from './ProjectTypeYamlEditorExtension'; - -export const ProjectTypeYamlEditorFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ProjectTypeYamlEditor', - component: ProjectTypeYamlEditorExtension, - validation: projectTypeYamlEditorValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/ProjectTypeYamlEditor/index.ts b/packages/portal-app/src/scaffolder/ProjectTypeYamlEditor/index.ts deleted file mode 100644 index 266dda8e6..000000000 --- a/packages/portal-app/src/scaffolder/ProjectTypeYamlEditor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ProjectTypeYamlEditorFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/ResourceNamePicker/extensions.ts b/packages/portal-app/src/scaffolder/ResourceNamePicker/extensions.ts deleted file mode 100644 index b0103b6ea..000000000 --- a/packages/portal-app/src/scaffolder/ResourceNamePicker/extensions.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - This is where the magic happens and creates the custom field extension. -*/ - -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - ResourceNamePicker, - resourceNamePickerValidation, -} from './ResourceNamePickerExtension'; - -export const ResourceNamePickerFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ResourceNamePicker', - component: ResourceNamePicker, - validation: resourceNamePickerValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/ResourceNamePicker/index.ts b/packages/portal-app/src/scaffolder/ResourceNamePicker/index.ts deleted file mode 100644 index 9e4be4b77..000000000 --- a/packages/portal-app/src/scaffolder/ResourceNamePicker/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ResourceNamePickerFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/ResourceParametersField/extensions.ts b/packages/portal-app/src/scaffolder/ResourceParametersField/extensions.ts deleted file mode 100644 index fa7cca81c..000000000 --- a/packages/portal-app/src/scaffolder/ResourceParametersField/extensions.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - ResourceParametersField, - ResourceParametersFieldSchema, -} from './ResourceParametersField'; - -export const ResourceParametersFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ResourceParametersField', - component: ResourceParametersField, - schema: ResourceParametersFieldSchema, - }), -); diff --git a/packages/portal-app/src/scaffolder/ResourceParametersField/index.ts b/packages/portal-app/src/scaffolder/ResourceParametersField/index.ts deleted file mode 100644 index 5b2502d0f..000000000 --- a/packages/portal-app/src/scaffolder/ResourceParametersField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ResourceParametersFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/ResourceTypeYamlEditor/extensions.ts b/packages/portal-app/src/scaffolder/ResourceTypeYamlEditor/extensions.ts deleted file mode 100644 index c98994fc1..000000000 --- a/packages/portal-app/src/scaffolder/ResourceTypeYamlEditor/extensions.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - ResourceTypeYamlEditorExtension, - resourceTypeYamlEditorValidation, -} from './ResourceTypeYamlEditorExtension'; - -export const ResourceTypeYamlEditorFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ResourceTypeYamlEditor', - component: ResourceTypeYamlEditorExtension, - validation: resourceTypeYamlEditorValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/ResourceTypeYamlEditor/index.ts b/packages/portal-app/src/scaffolder/ResourceTypeYamlEditor/index.ts deleted file mode 100644 index c2305a90f..000000000 --- a/packages/portal-app/src/scaffolder/ResourceTypeYamlEditor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ResourceTypeYamlEditorFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/SwitchField/extensions.ts b/packages/portal-app/src/scaffolder/SwitchField/extensions.ts deleted file mode 100644 index 974a76c11..000000000 --- a/packages/portal-app/src/scaffolder/SwitchField/extensions.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { SwitchField, SwitchFieldSchema } from './SwitchFieldExtension'; - -export const SwitchFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'SwitchField', - component: SwitchField, - schema: SwitchFieldSchema, - }), -); diff --git a/packages/portal-app/src/scaffolder/SwitchField/index.ts b/packages/portal-app/src/scaffolder/SwitchField/index.ts deleted file mode 100644 index 0231421a2..000000000 --- a/packages/portal-app/src/scaffolder/SwitchField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SwitchFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/TraitYamlEditor/extensions.ts b/packages/portal-app/src/scaffolder/TraitYamlEditor/extensions.ts deleted file mode 100644 index ca3591ef0..000000000 --- a/packages/portal-app/src/scaffolder/TraitYamlEditor/extensions.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - TraitYamlEditorExtension, - traitYamlEditorValidation, -} from './TraitYamlEditorExtension'; - -export const TraitYamlEditorFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'TraitYamlEditor', - component: TraitYamlEditorExtension, - validation: traitYamlEditorValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/TraitYamlEditor/index.ts b/packages/portal-app/src/scaffolder/TraitYamlEditor/index.ts deleted file mode 100644 index 493c5552d..000000000 --- a/packages/portal-app/src/scaffolder/TraitYamlEditor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { TraitYamlEditorFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/TraitsField/extensions.ts b/packages/portal-app/src/scaffolder/TraitsField/extensions.ts deleted file mode 100644 index 50b4a66f3..000000000 --- a/packages/portal-app/src/scaffolder/TraitsField/extensions.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - This is where the magic happens and creates the custom field extension for Traits. -*/ - -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { TraitsField, traitsFieldValidation } from './TraitsFieldExtension'; - -export const TraitsFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'TraitsField', - component: TraitsField, - validation: traitsFieldValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/TraitsField/index.ts b/packages/portal-app/src/scaffolder/TraitsField/index.ts deleted file mode 100644 index 756a434bb..000000000 --- a/packages/portal-app/src/scaffolder/TraitsField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { TraitsFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/WorkloadDetailsField/extensions.ts b/packages/portal-app/src/scaffolder/WorkloadDetailsField/extensions.ts deleted file mode 100644 index 3a134d936..000000000 --- a/packages/portal-app/src/scaffolder/WorkloadDetailsField/extensions.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; -import { - WorkloadDetailsField, - WorkloadDetailsFieldSchema, - workloadDetailsFieldValidation, -} from './WorkloadDetailsField'; - -export const WorkloadDetailsFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'WorkloadDetailsField', - component: WorkloadDetailsField, - schema: WorkloadDetailsFieldSchema, - validation: workloadDetailsFieldValidation, - }), -); diff --git a/packages/portal-app/src/scaffolder/WorkloadDetailsField/index.ts b/packages/portal-app/src/scaffolder/WorkloadDetailsField/index.ts deleted file mode 100644 index c58751198..000000000 --- a/packages/portal-app/src/scaffolder/WorkloadDetailsField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { WorkloadDetailsFieldExtension } from './extensions'; diff --git a/packages/portal-app/src/scaffolder/openChoreoTokenDecorator.ts b/packages/portal-app/src/scaffolder/openChoreoTokenDecorator.ts index 860be7fb9..d21cc4637 100644 --- a/packages/portal-app/src/scaffolder/openChoreoTokenDecorator.ts +++ b/packages/portal-app/src/scaffolder/openChoreoTokenDecorator.ts @@ -1,6 +1,6 @@ import { createScaffolderFormDecorator } from '@backstage/plugin-scaffolder-react/alpha'; import { configApiRef } from '@backstage/core-plugin-api'; -import { openChoreoAuthApiRef } from '../apis/authRefs'; +import { openChoreoAuthApiRef } from '@openchoreo/backstage-plugin'; /** * Form decorator that injects the user's OpenChoreo auth token as a secret. diff --git a/plugins/openchoreo-ci/README.md b/plugins/openchoreo-ci/README.md index 622180d7c..ee7876439 100644 --- a/plugins/openchoreo-ci/README.md +++ b/plugins/openchoreo-ci/README.md @@ -4,6 +4,12 @@ Welcome to the openchoreo-ci plugin! _This plugin was created through the Backstage CLI_ +## Requirements + +Requires [`@openchoreo/backstage-plugin`](../openchoreo) (the OpenChoreo +core plugin) to be installed in the same Backstage app. The core plugin +provides the fetch / permission / auth APIs this plugin uses. + ## Getting started Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/openchoreo-ci](http://localhost:3000/openchoreo-ci). diff --git a/plugins/openchoreo-ci/package.json b/plugins/openchoreo-ci/package.json index 81d48ac75..ecd45c554 100644 --- a/plugins/openchoreo-ci/package.json +++ b/plugins/openchoreo-ci/package.json @@ -70,6 +70,7 @@ "yaml": "^2.8.1" }, "peerDependencies": { + "@openchoreo/backstage-plugin": "^1.2.0", "react": "^18.0.0", "react-router-dom": "^6.0.0" }, diff --git a/plugins/openchoreo-observability/README.md b/plugins/openchoreo-observability/README.md index 439bd3c44..11bcaf87c 100644 --- a/plugins/openchoreo-observability/README.md +++ b/plugins/openchoreo-observability/README.md @@ -4,6 +4,13 @@ Welcome to the openchoreo-observability plugin! _This plugin was created through the Backstage CLI_ +## Requirements + +This plugin requires [`@openchoreo/backstage-plugin`](../openchoreo) (the +OpenChoreo core plugin) to be installed in the same Backstage app. The +core plugin registers the fetch / permission / auth API implementations +this plugin needs to reach the OpenChoreo backend. + ## Features - **Logs**: Component-level and project-level runtime logs with filtering diff --git a/plugins/openchoreo-observability/package.json b/plugins/openchoreo-observability/package.json index 5fdcff090..98079677d 100644 --- a/plugins/openchoreo-observability/package.json +++ b/plugins/openchoreo-observability/package.json @@ -69,6 +69,7 @@ "remend": "^1.0.0" }, "peerDependencies": { + "@openchoreo/backstage-plugin": "^1.2.0", "react": "^18.0.0", "react-router-dom": "^6.0.0" }, diff --git a/plugins/openchoreo-observability/src/alpha.test.tsx b/plugins/openchoreo-observability/src/alpha.test.tsx index 367d0e971..68f321ab3 100644 --- a/plugins/openchoreo-observability/src/alpha.test.tsx +++ b/plugins/openchoreo-observability/src/alpha.test.tsx @@ -44,6 +44,7 @@ describe('openchoreo-observability alpha plugin', () => { `entity-content:${plugin}/traces`, `entity-content:${plugin}/project-incidents`, `entity-content:${plugin}/rca-reports`, + `entity-content:${plugin}/project-cost-analysis`, // overview cards `entity-card:${plugin}/cost-insights-summary`, ]) { diff --git a/plugins/openchoreo-observability/src/alpha.tsx b/plugins/openchoreo-observability/src/alpha.tsx index 7d622fe1b..28c1bc31e 100644 --- a/plugins/openchoreo-observability/src/alpha.tsx +++ b/plugins/openchoreo-observability/src/alpha.tsx @@ -1,9 +1,11 @@ +import MonetizationOnIcon from '@material-ui/icons/MonetizationOn'; import { ApiBlueprint, createExtensionInput, createFrontendPlugin, discoveryApiRef, fetchApiRef, + PageBlueprint, PluginWrapperBlueprint, } from '@backstage/frontend-plugin-api'; import { @@ -282,6 +284,22 @@ const rcaReportsEntityContent = EntityContentBlueprint.make({ }, }); +const projectCostAnalysisEntityContent = EntityContentBlueprint.make({ + name: 'project-cost-analysis', + params: { + path: '/cost-analysis', + title: 'Cost Analysis', + group: 'analysis', + filter: isOpenChoreoManagedOfKind('system'), + loader: () => + import('./components/CostAnalysis/CostAnalysisPage').then(m => ( + + + + )), + }, +}); + /** * Cost Insights summary card, shown on the Component and Project (System) * overview pages. Filtered to entities carrying the openchoreo namespace @@ -307,15 +325,23 @@ const costInsightsSummaryCard = EntityCardBlueprint.make({ }, }); -/** - * NFS entry point for the OpenChoreo Observability plugin. - * - * Registers the three observability backend clients, the log-row-action - * registry API, the component-page entity tabs (Logs, Events, Metrics, - * Alerts, Wirelogs) and the system-page entity tabs (Logs, Traces, - * Incidents, RCA Reports), plus the Cost Insights summary card shown on the - * Component and Project overview pages. - */ +// Ships title + icon so adopters auto-get a sidebar entry via DefaultNavContent. +const costInsightsPage = PageBlueprint.make({ + name: 'cost-insights', + params: { + path: '/cost-insights', + routeRef: rootRouteRef, + title: 'Cost Insights', + icon: , + // Page renders its own
; suppress outer PageLayout header. + noHeader: true, + loader: () => + import('./components/CostInsights/CostInsightsPage').then(m => ( + + )), + }, +}); + export default createFrontendPlugin({ pluginId: 'openchoreo-observability', routes: { root: rootRouteRef }, @@ -325,6 +351,7 @@ export default createFrontendPlugin({ rcaAgentApi, finopsAgentApi, logRowActionRendererApi, + costInsightsPage, runtimeLogsEntityContent, runtimeEventsEntityContent, metricsEntityContent, @@ -334,6 +361,7 @@ export default createFrontendPlugin({ tracesEntityContent, projectIncidentsEntityContent, rcaReportsEntityContent, + projectCostAnalysisEntityContent, costInsightsSummaryCard, ], }); diff --git a/plugins/openchoreo-portal-assistant/README.md b/plugins/openchoreo-portal-assistant/README.md index 3598691da..f6f6afd15 100644 --- a/plugins/openchoreo-portal-assistant/README.md +++ b/plugins/openchoreo-portal-assistant/README.md @@ -5,6 +5,12 @@ the chat drawer, the global FAB, and the contextual launchers mounted on component pages (failed-build snackbar, build-tab launcher, logs-tab "Ask Perch" pill). +## Requirements + +Requires [`@openchoreo/backstage-plugin`](../openchoreo) (the OpenChoreo +core plugin) to be installed in the same Backstage app. The core plugin +provides the fetch / permission / auth APIs this plugin uses. + ## Status: internal plugin This plugin is part of the OpenChoreo Backstage app. It is **not** published diff --git a/plugins/openchoreo-portal-assistant/package.json b/plugins/openchoreo-portal-assistant/package.json index 3e0dda0d5..7ad9dbb1d 100644 --- a/plugins/openchoreo-portal-assistant/package.json +++ b/plugins/openchoreo-portal-assistant/package.json @@ -48,6 +48,7 @@ "remend": "^1.0.0" }, "peerDependencies": { + "@openchoreo/backstage-plugin": "^1.2.0", "react": "18.0.0", "react-router-dom": "6.0.0" }, diff --git a/plugins/openchoreo-react/src/hooks/useQueryParams.ts b/plugins/openchoreo-react/src/hooks/useQueryParams.ts new file mode 100644 index 000000000..8d2f4b3f4 --- /dev/null +++ b/plugins/openchoreo-react/src/hooks/useQueryParams.ts @@ -0,0 +1,109 @@ +import { useCallback, useMemo, useRef } from 'react'; +import { useSearchParams } from 'react-router-dom'; + +type ParamValue = string | string[] | undefined; + +interface ParamConfig { + /** Default value when param is not in URL */ + defaultValue?: ParamValue; + /** Custom parser for the URL string value */ + parse?: (value: string | null) => ParamValue; + /** Custom serializer for setting the URL value */ + serialize?: (value: ParamValue) => string | undefined; +} + +/** + * Hook for managing URL query parameters with type-safe defaults and serialization. + * + * @example + * ```tsx + * const [params, setParams] = useQueryParams({ + * tab: { defaultValue: 'runs' }, + * env: { defaultValue: undefined }, + * components: { + * defaultValue: [], + * parse: (v) => v?.split(',').filter(Boolean) || [], + * serialize: (v) => Array.isArray(v) && v.length > 0 ? v.join(',') : undefined, + * }, + * }); + * + * // Read values + * console.log(params.tab, params.env, params.components); + * + * // Update values (merges with existing) + * setParams({ tab: 'configurations' }); + * ``` + */ +export function useQueryParams>( + config: Record, +): [T, (updates: Partial, options?: { replace?: boolean }) => void] { + const [searchParams, setSearchParams] = useSearchParams(); + + // Callers typically pass an inline config literal, so `config` gets a new + // identity every render. Hold the latest in refs and drive memoization + // from URL state only, so `values`/`setValues` don't churn each render + // (which would re-trigger any consumer effect that depends on them). + const configRef = useRef(config); + configRef.current = config; + const searchParamsRef = useRef(searchParams); + searchParamsRef.current = searchParams; + + const values = useMemo(() => { + const cfg = configRef.current; + const result = {} as Record; + + for (const [key, conf] of Object.entries(cfg)) { + const raw = searchParams.get(key); + + if (conf.parse) { + result[key] = conf.parse(raw); + } else if (raw === null) { + result[key] = conf.defaultValue; + } else { + result[key] = raw; + } + } + + return result as T; + }, [searchParams]); + + const setValues = useCallback( + (updates: Partial, options?: { replace?: boolean }) => { + const cfg = configRef.current; + const newParams = new URLSearchParams(searchParamsRef.current); + + for (const [key, value] of Object.entries(updates)) { + const conf = cfg[key as keyof T]; + + if (value === undefined || value === null) { + newParams.delete(key); + } else if (conf?.serialize) { + const serialized = conf.serialize(value as ParamValue); + if (serialized !== undefined) { + newParams.set(key, serialized); + } else { + newParams.delete(key); + } + } else if (Array.isArray(value)) { + if (value.length > 0) { + newParams.set(key, value.join(',')); + } else { + newParams.delete(key); + } + } else { + const defaultVal = conf?.defaultValue; + if (value === defaultVal) { + newParams.delete(key); + } else { + newParams.set(key, String(value)); + } + } + } + + setSearchParams(newParams, { replace: options?.replace ?? true }); + }, + [setSearchParams], + ); + + return [values, setValues]; +} diff --git a/plugins/openchoreo-react/src/index.ts b/plugins/openchoreo-react/src/index.ts index a440eed6b..7a02e8e8d 100644 --- a/plugins/openchoreo-react/src/index.ts +++ b/plugins/openchoreo-react/src/index.ts @@ -187,6 +187,7 @@ export { useAutoLoadWhenEmpty, type UseAutoLoadWhenEmptyOptions, } from './hooks/useAutoLoadWhenEmpty'; +export { useQueryParams } from './hooks/useQueryParams'; export { useEntityAnnotation, useHasAnnotation, diff --git a/plugins/openchoreo-workflows/README.md b/plugins/openchoreo-workflows/README.md new file mode 100644 index 000000000..49e6f3566 --- /dev/null +++ b/plugins/openchoreo-workflows/README.md @@ -0,0 +1,10 @@ +# openchoreo-workflows + +Backstage frontend plugin for OpenChoreo generic workflow runs. + +## Requirements + +Requires [`@openchoreo/backstage-plugin`](../openchoreo) (the OpenChoreo +core plugin) to be installed in the same Backstage app. The core plugin +provides the fetch / permission / auth APIs this plugin uses to reach the +OpenChoreo backend. diff --git a/plugins/openchoreo-workflows/package.json b/plugins/openchoreo-workflows/package.json index 28d72836a..1c845efcf 100644 --- a/plugins/openchoreo-workflows/package.json +++ b/plugins/openchoreo-workflows/package.json @@ -67,6 +67,7 @@ "yaml": "^2.8.2" }, "peerDependencies": { + "@openchoreo/backstage-plugin": "^1.2.0", "react": "^18.0.0", "react-router-dom": "^6.0.0" }, diff --git a/plugins/openchoreo/package.json b/plugins/openchoreo/package.json index 6849ee920..232668d5b 100644 --- a/plugins/openchoreo/package.json +++ b/plugins/openchoreo/package.json @@ -50,6 +50,8 @@ }, "dependencies": { "@backstage/catalog-model": "^1.9.0", + "@backstage/config": "^1.3.8", + "@backstage/core-app-api": "^1.20.1", "@backstage/core-components": "^0.18.10", "@backstage/core-plugin-api": "^1.12.6", "@backstage/errors": "^1.3.1", @@ -60,7 +62,9 @@ "@backstage/plugin-catalog-common": "^1.1.10", "@backstage/plugin-catalog-graph": "^0.6.4", "@backstage/plugin-catalog-react": "^3.0.0", + "@backstage/plugin-permission-common": "^0.9.9", "@backstage/plugin-permission-react": "^0.5.1", + "@backstage/plugin-scaffolder-react": "^2.0.0", "@backstage/plugin-techdocs-common": "^0.1.1", "@backstage/theme": "^0.7.3", "@dagrejs/dagre": "^2.0.4", @@ -100,7 +104,6 @@ }, "devDependencies": { "@backstage/cli": "^0.36.2", - "@backstage/core-app-api": "^1.20.1", "@backstage/dev-utils": "^1.1.23", "@backstage/test-utils": "^1.7.18", "@openchoreo/test-utils": "workspace:^", diff --git a/plugins/openchoreo/src/alpha.test.tsx b/plugins/openchoreo/src/alpha.test.tsx index 9f1a69faf..8ac906991 100644 --- a/plugins/openchoreo/src/alpha.test.tsx +++ b/plugins/openchoreo/src/alpha.test.tsx @@ -3,8 +3,12 @@ import openchoreoPlugin from './alpha'; const ALPHA_EXTENSION_NAMES = [ // backend client ['api', 'open-choreo-client'], + // OpenChoreo IDP auth (fetch/permission overrides live in openChoreoAppModule) + ['api', 'openchoreo-auth'], // self-contained response-cache provider ['plugin-wrapper', 'query-provider'], + // routed page (opened via window.open from the resource drawer) + ['page', 'exec-terminal'], // entity context menu items (delete + annotation edit) ['entity-context-menu-item', 'delete-entity'], ['entity-context-menu-item', 'edit-annotations'], @@ -15,7 +19,9 @@ const ALPHA_EXTENSION_NAMES = [ ['entity-card', 'deployment-status'], ['entity-card', 'runtime-health'], // system-page + ['entity-content', 'project-deploy'], ['entity-content', 'cell-diagram'], + ['entity-content', 'project-diagram'], ['entity-card', 'project-contents'], ['entity-card', 'deployment-pipeline'], // domain-page @@ -56,6 +62,39 @@ const ALPHA_EXTENSION_NAMES = [ // workflow family ['entity-card', 'workflow-overview'], ['entity-card', 'component-workflow-overview'], + // scaffolder form fields + ['scaffolder-form-field', 'advanced-configuration-field'], + ['scaffolder-form-field', 'build-and-deploy-field'], + ['scaffolder-form-field', 'build-template-parameters'], + ['scaffolder-form-field', 'build-template-picker'], + ['scaffolder-form-field', 'build-workflow-parameters'], + ['scaffolder-form-field', 'build-workflow-picker'], + ['scaffolder-form-field', 'cluster-component-type-yaml-editor'], + ['scaffolder-form-field', 'cluster-project-type-yaml-editor'], + ['scaffolder-form-field', 'cluster-resource-type-yaml-editor'], + ['scaffolder-form-field', 'cluster-trait-yaml-editor'], + ['scaffolder-form-field', 'cluster-workflow-yaml-editor'], + ['scaffolder-form-field', 'component-name-picker'], + ['scaffolder-form-field', 'component-type-yaml-editor'], + ['scaffolder-form-field', 'component-workflow-yaml-editor'], + ['scaffolder-form-field', 'container-image-field'], + ['scaffolder-form-field', 'deployment-pipeline-form-with-yaml'], + ['scaffolder-form-field', 'deployment-pipeline-picker'], + ['scaffolder-form-field', 'deployment-source-picker'], + ['scaffolder-form-field', 'environment-form-with-yaml'], + ['scaffolder-form-field', 'git-source-field'], + ['scaffolder-form-field', 'namespace-entity-picker'], + ['scaffolder-form-field', 'notification-channel-form-with-yaml'], + ['scaffolder-form-field', 'project-namespace-field'], + ['scaffolder-form-field', 'project-parameters-field'], + ['scaffolder-form-field', 'project-type-yaml-editor'], + ['scaffolder-form-field', 'resource-name-picker'], + ['scaffolder-form-field', 'resource-parameters-field'], + ['scaffolder-form-field', 'resource-type-yaml-editor'], + ['scaffolder-form-field', 'switch-field'], + ['scaffolder-form-field', 'trait-yaml-editor'], + ['scaffolder-form-field', 'traits-field'], + ['scaffolder-form-field', 'workload-details-field'], // per-kind Overview layouts ['entity-content-layout', 'component-overview'], ['entity-content-layout', 'system-overview'], diff --git a/plugins/openchoreo/src/alpha.tsx b/plugins/openchoreo/src/alpha.tsx index 3f45afa42..8bc394494 100644 --- a/plugins/openchoreo/src/alpha.tsx +++ b/plugins/openchoreo/src/alpha.tsx @@ -1,10 +1,14 @@ import { ApiBlueprint, + configApiRef, createFrontendPlugin, discoveryApiRef, fetchApiRef, + oauthRequestApiRef, + PageBlueprint, PluginWrapperBlueprint, } from '@backstage/frontend-plugin-api'; +import { OAuth2 } from '@backstage/core-app-api'; import { EntityCardBlueprint, EntityContentBlueprint, @@ -28,14 +32,17 @@ import { } from './components/AnnotationEditor/useAnnotationEditorContextMenuItemProps'; export { openChoreoEntityPageOverride } from './extensions/openChoreoEntityPageOverride'; +export { openChoreoAppModule } from './appModule'; import { rootCatalogEnvironmentRouteRef, accessControlRouteRef, + execTerminalRouteRef, resourceEnvironmentsRouteRef, } from './routes'; import { openChoreoClientApiRef } from './api/OpenChoreoClientApi'; import { OpenChoreoClient } from './api/OpenChoreoClient'; +import { openChoreoAuthApiRef } from './api/authRefs'; const openChoreoClientApi = ApiBlueprint.make({ name: 'open-choreo-client', @@ -48,6 +55,44 @@ const openChoreoClientApi = ApiBlueprint.make({ }), }); +// OAuth2 client for the OpenChoreo IDP. Consumed by the openChoreoAppModule's +// fetch/permission overrides and by the portal's SignInPage. +const openChoreoAuthApi = ApiBlueprint.make({ + name: 'openchoreo-auth', + params: defineParams => + defineParams({ + api: openChoreoAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => { + const env = + configApi.getOptionalString('auth.environment') ?? 'development'; + const scopeStr = configApi.getOptionalString( + 'openchoreo.features.auth.scope', + ); + const scopes = scopeStr?.split(/\s+/).filter(Boolean) ?? []; + const defaultScopes = scopes.length + ? scopes + : ['openid', 'profile', 'email']; + return OAuth2.create({ + discoveryApi, + oauthRequestApi, + configApi, + provider: { + id: 'openchoreo-auth', + title: 'OpenChoreo', + icon: () => null, + }, + environment: env, + defaultScopes, + }); + }, + }), +}); + // Wraps this plugin's own extensions (tabs/cards) in the TanStack Query // provider, so every OpenChoreo surface has a QueryClient in the tree — // response caching is self-contained, the host wires nothing. Uses @@ -74,9 +119,9 @@ const resourceDefinitionEntityContent = EntityContentBlueprint.make({ path: '/definition', title: 'Definition', group: 'definition', - // Any OC-managed entity gets the Definition tab. The MANAGED label - // already implies OC-owned; a separate kind list is redundant. - filter: isOpenChoreoManagedEntity, + // API entities are excluded — upstream api-docs owns their /definition tab. + filter: entity => + isOpenChoreoManagedEntity(entity) && entity.kind.toLowerCase() !== 'api', loader: () => import('./components/ResourceDefinition').then(m => ( @@ -126,6 +171,20 @@ const runtimeHealthCard = EntityCardBlueprint.make({ }); // ─── System (project) page tabs + cards (kind:system) ───────────────────── +const projectDeployEntityContent = EntityContentBlueprint.make({ + name: 'project-deploy', + params: { + path: '/deploy', + title: 'Deploy', + group: 'deployment', + filter: isOpenChoreoManagedOfKind('system'), + loader: () => + import('./components/ProjectEnvironments').then(m => ( + + )), + }, +}); + const cellDiagramEntityContent = EntityContentBlueprint.make({ name: 'cell-diagram', params: { @@ -140,6 +199,39 @@ const cellDiagramEntityContent = EntityContentBlueprint.make({ }, }); +const projectDiagramEntityContent = EntityContentBlueprint.make({ + name: 'project-diagram', + params: { + path: '/diagram', + title: 'Diagram', + group: 'deployment', + filter: isOpenChoreoManagedOfKind('system'), + loader: () => + Promise.all([ + import('./components/ContainedCatalogGraphCard'), + import('@backstage/plugin-catalog-graph'), + import('@backstage/catalog-model'), + ]).then(([contained, graph, model]) => ( + + )), + }, +}); + const projectContentsCard = EntityCardBlueprint.make({ name: 'project-contents', params: { @@ -754,23 +846,47 @@ const componentWorkflowOverviewLayout = EntityContentLayoutBlueprint.make({ }, }); +// Scaffolder form field extensions. Adopters get the fields registered +// automatically when they install this plugin, so any OC template +// referencing `ui:field: ` renders correctly. +import { scaffolderFieldExtensions } from './scaffolder/extensions'; + +// Opened via window.open() from the resource drawer; no title/icon = no nav item. +const execTerminalPage = PageBlueprint.make({ + name: 'exec-terminal', + params: { + path: '/exec-terminal', + routeRef: execTerminalRouteRef, + noHeader: true, + loader: () => + import('./components/Terminal/ExecTerminalWindowPage').then(m => ( + + )), + }, +}); + export default createFrontendPlugin({ pluginId: 'openchoreo', routes: { catalogEnvironment: rootCatalogEnvironmentRouteRef, accessControl: accessControlRouteRef, + execTerminal: execTerminalRouteRef, resourceEnvironments: resourceEnvironmentsRouteRef, }, extensions: [ openChoreoClientApi, + openChoreoAuthApi, queryProvider, + execTerminalPage, deleteEntityContextMenuItem, editAnnotationsEntityContextMenuItem, resourceDefinitionEntityContent, componentDeployEntityContent, deploymentStatusCard, runtimeHealthCard, + projectDeployEntityContent, cellDiagramEntityContent, + projectDiagramEntityContent, projectContentsCard, deploymentPipelineCard, namespaceProjectsCard, @@ -802,6 +918,7 @@ export default createFrontendPlugin({ traitTypeOverviewCard, workflowOverviewCard, componentWorkflowOverviewCard, + ...scaffolderFieldExtensions, // per-kind Overview layouts componentServiceOverviewLayout, systemOverviewLayout, diff --git a/packages/portal-app/src/apis/OpenChoreoFetchApi.test.ts b/plugins/openchoreo/src/api/OpenChoreoFetchApi.test.ts similarity index 100% rename from packages/portal-app/src/apis/OpenChoreoFetchApi.test.ts rename to plugins/openchoreo/src/api/OpenChoreoFetchApi.test.ts diff --git a/packages/portal-app/src/apis/OpenChoreoFetchApi.ts b/plugins/openchoreo/src/api/OpenChoreoFetchApi.ts similarity index 100% rename from packages/portal-app/src/apis/OpenChoreoFetchApi.ts rename to plugins/openchoreo/src/api/OpenChoreoFetchApi.ts diff --git a/packages/portal-app/src/apis/OpenChoreoPermissionApi.test.ts b/plugins/openchoreo/src/api/OpenChoreoPermissionApi.test.ts similarity index 100% rename from packages/portal-app/src/apis/OpenChoreoPermissionApi.test.ts rename to plugins/openchoreo/src/api/OpenChoreoPermissionApi.test.ts diff --git a/packages/portal-app/src/apis/OpenChoreoPermissionApi.ts b/plugins/openchoreo/src/api/OpenChoreoPermissionApi.ts similarity index 100% rename from packages/portal-app/src/apis/OpenChoreoPermissionApi.ts rename to plugins/openchoreo/src/api/OpenChoreoPermissionApi.ts diff --git a/packages/portal-app/src/apis/authRefs.ts b/plugins/openchoreo/src/api/authRefs.ts similarity index 100% rename from packages/portal-app/src/apis/authRefs.ts rename to plugins/openchoreo/src/api/authRefs.ts diff --git a/plugins/openchoreo/src/appModule.tsx b/plugins/openchoreo/src/appModule.tsx new file mode 100644 index 000000000..f76c6d7c8 --- /dev/null +++ b/plugins/openchoreo/src/appModule.tsx @@ -0,0 +1,57 @@ +import { + ApiBlueprint, + configApiRef, + createFrontendModule, + discoveryApiRef, + fetchApiRef, + identityApiRef, +} from '@backstage/frontend-plugin-api'; +import { permissionApiRef } from '@backstage/plugin-permission-react'; +import { openChoreoAuthApiRef } from './api/authRefs'; +import { OpenChoreoFetchApi } from './api/OpenChoreoFetchApi'; +import { OpenChoreoPermissionApi } from './api/OpenChoreoPermissionApi'; + +// Attached to pluginId 'app' so extension IDs match Backstage's default +// api extensions (api:app/core.fetch, api:app/plugin.permission.api) and +// override them. Registering these under pluginId 'openchoreo' would +// trigger API_FACTORY_CONFLICT at app startup. +const openChoreoFetchApi = ApiBlueprint.make({ + name: fetchApiRef.id, + params: defineParams => + defineParams({ + api: fetchApiRef, + deps: { + identityApi: identityApiRef, + oauthApi: openChoreoAuthApiRef, + configApi: configApiRef, + }, + factory: ({ identityApi, oauthApi, configApi }) => + new OpenChoreoFetchApi(identityApi, oauthApi, configApi), + }), +}); + +const openChoreoPermissionApi = ApiBlueprint.make({ + name: permissionApiRef.id, + params: defineParams => + defineParams({ + api: permissionApiRef, + deps: { + configApi: configApiRef, + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + oauthApi: openChoreoAuthApiRef, + }, + factory: ({ configApi, discoveryApi, identityApi, oauthApi }) => + new OpenChoreoPermissionApi({ + config: configApi, + discovery: discoveryApi, + identity: identityApi, + oauthApi, + }), + }), +}); + +export const openChoreoAppModule = createFrontendModule({ + pluginId: 'app', + extensions: [openChoreoFetchApi, openChoreoPermissionApi], +}); diff --git a/plugins/openchoreo/src/components/AccessControl/AccessControlPage.test.tsx b/plugins/openchoreo/src/components/AccessControl/AccessControlPage.test.tsx index 3622c168a..fbf9a57f5 100644 --- a/plugins/openchoreo/src/components/AccessControl/AccessControlPage.test.tsx +++ b/plugins/openchoreo/src/components/AccessControl/AccessControlPage.test.tsx @@ -45,6 +45,10 @@ jest.mock('./hooks', () => ({ useClusterRoles: () => mockUseClusterRoles(), })); +jest.mock('../../hooks/useQueryParams', () => ({ + useQueryParams: () => [{ tab: '', action: '' }, jest.fn()], +})); + jest.mock('./RolesTab', () => ({ RolesTab: () =>
RolesTab
, })); diff --git a/plugins/openchoreo/src/extensions/openChoreoEntityPageOverride.tsx b/plugins/openchoreo/src/extensions/openChoreoEntityPageOverride.tsx index 299d1f055..2ad86d6ab 100644 --- a/plugins/openchoreo/src/extensions/openChoreoEntityPageOverride.tsx +++ b/plugins/openchoreo/src/extensions/openChoreoEntityPageOverride.tsx @@ -93,6 +93,7 @@ function buildFilterFn( const GROUP_ORDER = [ 'overview', 'definition', + 'documentation', 'deployment', 'runtime', 'analysis', diff --git a/plugins/openchoreo/src/hooks/useQueryParams.ts b/plugins/openchoreo/src/hooks/useQueryParams.ts index aa5826ef4..e1dade5b8 100644 --- a/plugins/openchoreo/src/hooks/useQueryParams.ts +++ b/plugins/openchoreo/src/hooks/useQueryParams.ts @@ -1,99 +1,2 @@ -import { useCallback, useMemo } from 'react'; -import { useSearchParams } from 'react-router-dom'; - -type ParamValue = string | string[] | undefined; - -interface ParamConfig { - /** Default value when param is not in URL */ - defaultValue?: ParamValue; - /** Custom parser for the URL string value */ - parse?: (value: string | null) => ParamValue; - /** Custom serializer for setting the URL value */ - serialize?: (value: ParamValue) => string | undefined; -} - -/** - * Hook for managing URL query parameters with type-safe defaults and serialization. - * - * @example - * ```tsx - * const [params, setParams] = useQueryParams({ - * tab: { defaultValue: 'runs' }, - * env: { defaultValue: undefined }, - * components: { - * defaultValue: [], - * parse: (v) => v?.split(',').filter(Boolean) || [], - * serialize: (v) => Array.isArray(v) && v.length > 0 ? v.join(',') : undefined, - * }, - * }); - * - * // Read values - * console.log(params.tab, params.env, params.components); - * - * // Update values (merges with existing) - * setParams({ tab: 'configurations' }); - * ``` - */ -export function useQueryParams>( - config: Record, -): [T, (updates: Partial, options?: { replace?: boolean }) => void] { - const [searchParams, setSearchParams] = useSearchParams(); - - const values = useMemo(() => { - const result = {} as Record; - - for (const [key, conf] of Object.entries(config)) { - const raw = searchParams.get(key); - - if (conf.parse) { - result[key] = conf.parse(raw); - } else if (raw === null) { - result[key] = conf.defaultValue; - } else { - result[key] = raw; - } - } - - return result as T; - }, [searchParams, config]); - - const setValues = useCallback( - (updates: Partial, options?: { replace?: boolean }) => { - const newParams = new URLSearchParams(searchParams); - - for (const [key, value] of Object.entries(updates)) { - const conf = config[key as keyof T]; - - if (value === undefined || value === null) { - newParams.delete(key); - } else if (conf?.serialize) { - const serialized = conf.serialize(value as ParamValue); - if (serialized !== undefined) { - newParams.set(key, serialized); - } else { - newParams.delete(key); - } - } else if (Array.isArray(value)) { - if (value.length > 0) { - newParams.set(key, value.join(',')); - } else { - newParams.delete(key); - } - } else { - // Check if value equals default - if so, remove from URL - const defaultVal = conf?.defaultValue; - if (value === defaultVal) { - newParams.delete(key); - } else { - newParams.set(key, String(value)); - } - } - } - - setSearchParams(newParams, { replace: options?.replace ?? true }); - }, - [searchParams, setSearchParams, config], - ); - - return [values, setValues]; -} +// Backwards-compat re-export; canonical home is `@openchoreo/backstage-plugin-react`. +export { useQueryParams } from '@openchoreo/backstage-plugin-react'; diff --git a/plugins/openchoreo/src/index.ts b/plugins/openchoreo/src/index.ts index da95dc94d..73503c2cf 100644 --- a/plugins/openchoreo/src/index.ts +++ b/plugins/openchoreo/src/index.ts @@ -1,5 +1,8 @@ export { choreoPlugin } from './plugin'; export { openChoreoClientApiRef } from './api/OpenChoreoClientApi'; +export { openChoreoAuthApiRef } from './api/authRefs'; +export { OpenChoreoFetchApi } from './api/OpenChoreoFetchApi'; +export { OpenChoreoPermissionApi } from './api/OpenChoreoPermissionApi'; // Exported so external Backstage hosts can register the API factory in // their packages/app/src/apis.ts. Without this, hosts that do not include // the plugin in their auto-discovered routes tree get a NotImplementedError @@ -100,6 +103,15 @@ export { ComponentWorkflowOverviewCard } from './components/ComponentWorkflowOve export { ResourceDefinitionTab } from './components/ResourceDefinition'; export { OpenChoreoAboutCard } from './components/OpenChoreoAboutCard'; export { ContainedCatalogGraphCard } from './components/ContainedCatalogGraphCard'; +export { EntityWarningStrip } from './extensions/entityLayouts/EntityWarningStrip'; +export { + ScaffolderPreselectionProvider, + useScaffolderPreselection, +} from './scaffolder/ScaffolderPreselectionContext'; +export { + ForeignCardsSection, + selectForeignCards, +} from './extensions/entityLayouts/foreignCards'; export { EntityRelationWarning } from './components/EntityRelationWarning'; export { useQueryParams } from './hooks/useQueryParams'; export { ExecTerminalWindowPage } from './components/Terminal'; diff --git a/plugins/openchoreo/src/routes.ts b/plugins/openchoreo/src/routes.ts index dc1c93904..129dc9fe7 100644 --- a/plugins/openchoreo/src/routes.ts +++ b/plugins/openchoreo/src/routes.ts @@ -12,3 +12,6 @@ export const accessControlRouteRef = createRouteRef({ export const resourceEnvironmentsRouteRef = createRouteRef({ id: 'resource-environments', }); +export const execTerminalRouteRef = createRouteRef({ + id: 'exec-terminal', +}); diff --git a/packages/portal-app/src/scaffolder/AdvancedConfigurationField/AdvancedConfigurationField.tsx b/plugins/openchoreo/src/scaffolder/AdvancedConfigurationField/AdvancedConfigurationField.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/AdvancedConfigurationField/AdvancedConfigurationField.tsx rename to plugins/openchoreo/src/scaffolder/AdvancedConfigurationField/AdvancedConfigurationField.tsx diff --git a/packages/portal-app/src/scaffolder/BuildAndDeployField/BuildAndDeployField.tsx b/plugins/openchoreo/src/scaffolder/BuildAndDeployField/BuildAndDeployField.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/BuildAndDeployField/BuildAndDeployField.tsx rename to plugins/openchoreo/src/scaffolder/BuildAndDeployField/BuildAndDeployField.tsx diff --git a/packages/portal-app/src/scaffolder/BuildTemplateParameters/BuildTemplateParametersExtension.tsx b/plugins/openchoreo/src/scaffolder/BuildTemplateParameters/BuildTemplateParametersExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/BuildTemplateParameters/BuildTemplateParametersExtension.tsx rename to plugins/openchoreo/src/scaffolder/BuildTemplateParameters/BuildTemplateParametersExtension.tsx diff --git a/packages/portal-app/src/scaffolder/BuildTemplatePicker/BuildTemplatePickerExtension.tsx b/plugins/openchoreo/src/scaffolder/BuildTemplatePicker/BuildTemplatePickerExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/BuildTemplatePicker/BuildTemplatePickerExtension.tsx rename to plugins/openchoreo/src/scaffolder/BuildTemplatePicker/BuildTemplatePickerExtension.tsx diff --git a/packages/portal-app/src/scaffolder/BuildWorkflowParameters/BuildWorkflowParametersExtension.tsx b/plugins/openchoreo/src/scaffolder/BuildWorkflowParameters/BuildWorkflowParametersExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/BuildWorkflowParameters/BuildWorkflowParametersExtension.tsx rename to plugins/openchoreo/src/scaffolder/BuildWorkflowParameters/BuildWorkflowParametersExtension.tsx diff --git a/packages/portal-app/src/scaffolder/BuildWorkflowPicker/BuildWorkflowPickerExtension.test.tsx b/plugins/openchoreo/src/scaffolder/BuildWorkflowPicker/BuildWorkflowPickerExtension.test.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/BuildWorkflowPicker/BuildWorkflowPickerExtension.test.tsx rename to plugins/openchoreo/src/scaffolder/BuildWorkflowPicker/BuildWorkflowPickerExtension.test.tsx diff --git a/packages/portal-app/src/scaffolder/BuildWorkflowPicker/BuildWorkflowPickerExtension.tsx b/plugins/openchoreo/src/scaffolder/BuildWorkflowPicker/BuildWorkflowPickerExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/BuildWorkflowPicker/BuildWorkflowPickerExtension.tsx rename to plugins/openchoreo/src/scaffolder/BuildWorkflowPicker/BuildWorkflowPickerExtension.tsx diff --git a/packages/portal-app/src/scaffolder/ClusterComponentTypeYamlEditor/ClusterComponentTypeYamlEditorExtension.tsx b/plugins/openchoreo/src/scaffolder/ClusterComponentTypeYamlEditor/ClusterComponentTypeYamlEditorExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ClusterComponentTypeYamlEditor/ClusterComponentTypeYamlEditorExtension.tsx rename to plugins/openchoreo/src/scaffolder/ClusterComponentTypeYamlEditor/ClusterComponentTypeYamlEditorExtension.tsx diff --git a/packages/portal-app/src/scaffolder/ClusterComponentTypeYamlEditor/styles.ts b/plugins/openchoreo/src/scaffolder/ClusterComponentTypeYamlEditor/styles.ts similarity index 100% rename from packages/portal-app/src/scaffolder/ClusterComponentTypeYamlEditor/styles.ts rename to plugins/openchoreo/src/scaffolder/ClusterComponentTypeYamlEditor/styles.ts diff --git a/packages/portal-app/src/scaffolder/ClusterProjectTypeYamlEditor/ClusterProjectTypeYamlEditorExtension.test.tsx b/plugins/openchoreo/src/scaffolder/ClusterProjectTypeYamlEditor/ClusterProjectTypeYamlEditorExtension.test.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ClusterProjectTypeYamlEditor/ClusterProjectTypeYamlEditorExtension.test.tsx rename to plugins/openchoreo/src/scaffolder/ClusterProjectTypeYamlEditor/ClusterProjectTypeYamlEditorExtension.test.tsx diff --git a/packages/portal-app/src/scaffolder/ClusterProjectTypeYamlEditor/ClusterProjectTypeYamlEditorExtension.tsx b/plugins/openchoreo/src/scaffolder/ClusterProjectTypeYamlEditor/ClusterProjectTypeYamlEditorExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ClusterProjectTypeYamlEditor/ClusterProjectTypeYamlEditorExtension.tsx rename to plugins/openchoreo/src/scaffolder/ClusterProjectTypeYamlEditor/ClusterProjectTypeYamlEditorExtension.tsx diff --git a/packages/portal-app/src/scaffolder/ClusterProjectTypeYamlEditor/styles.ts b/plugins/openchoreo/src/scaffolder/ClusterProjectTypeYamlEditor/styles.ts similarity index 100% rename from packages/portal-app/src/scaffolder/ClusterProjectTypeYamlEditor/styles.ts rename to plugins/openchoreo/src/scaffolder/ClusterProjectTypeYamlEditor/styles.ts diff --git a/packages/portal-app/src/scaffolder/ClusterResourceTypeYamlEditor/ClusterResourceTypeYamlEditorExtension.tsx b/plugins/openchoreo/src/scaffolder/ClusterResourceTypeYamlEditor/ClusterResourceTypeYamlEditorExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ClusterResourceTypeYamlEditor/ClusterResourceTypeYamlEditorExtension.tsx rename to plugins/openchoreo/src/scaffolder/ClusterResourceTypeYamlEditor/ClusterResourceTypeYamlEditorExtension.tsx diff --git a/packages/portal-app/src/scaffolder/ClusterResourceTypeYamlEditor/styles.ts b/plugins/openchoreo/src/scaffolder/ClusterResourceTypeYamlEditor/styles.ts similarity index 100% rename from packages/portal-app/src/scaffolder/ClusterResourceTypeYamlEditor/styles.ts rename to plugins/openchoreo/src/scaffolder/ClusterResourceTypeYamlEditor/styles.ts diff --git a/packages/portal-app/src/scaffolder/ClusterTraitYamlEditor/ClusterTraitYamlEditorExtension.tsx b/plugins/openchoreo/src/scaffolder/ClusterTraitYamlEditor/ClusterTraitYamlEditorExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ClusterTraitYamlEditor/ClusterTraitYamlEditorExtension.tsx rename to plugins/openchoreo/src/scaffolder/ClusterTraitYamlEditor/ClusterTraitYamlEditorExtension.tsx diff --git a/packages/portal-app/src/scaffolder/ClusterTraitYamlEditor/styles.ts b/plugins/openchoreo/src/scaffolder/ClusterTraitYamlEditor/styles.ts similarity index 100% rename from packages/portal-app/src/scaffolder/ClusterTraitYamlEditor/styles.ts rename to plugins/openchoreo/src/scaffolder/ClusterTraitYamlEditor/styles.ts diff --git a/packages/portal-app/src/scaffolder/ClusterWorkflowYamlEditor/ClusterWorkflowYamlEditorExtension.tsx b/plugins/openchoreo/src/scaffolder/ClusterWorkflowYamlEditor/ClusterWorkflowYamlEditorExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ClusterWorkflowYamlEditor/ClusterWorkflowYamlEditorExtension.tsx rename to plugins/openchoreo/src/scaffolder/ClusterWorkflowYamlEditor/ClusterWorkflowYamlEditorExtension.tsx diff --git a/packages/portal-app/src/scaffolder/ClusterWorkflowYamlEditor/styles.ts b/plugins/openchoreo/src/scaffolder/ClusterWorkflowYamlEditor/styles.ts similarity index 100% rename from packages/portal-app/src/scaffolder/ClusterWorkflowYamlEditor/styles.ts rename to plugins/openchoreo/src/scaffolder/ClusterWorkflowYamlEditor/styles.ts diff --git a/packages/portal-app/src/scaffolder/ComponentNamePicker/ComponentNamePickerExtension.tsx b/plugins/openchoreo/src/scaffolder/ComponentNamePicker/ComponentNamePickerExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ComponentNamePicker/ComponentNamePickerExtension.tsx rename to plugins/openchoreo/src/scaffolder/ComponentNamePicker/ComponentNamePickerExtension.tsx diff --git a/packages/portal-app/src/scaffolder/ComponentTypeYamlEditor/ComponentTypeYamlEditorExtension.tsx b/plugins/openchoreo/src/scaffolder/ComponentTypeYamlEditor/ComponentTypeYamlEditorExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ComponentTypeYamlEditor/ComponentTypeYamlEditorExtension.tsx rename to plugins/openchoreo/src/scaffolder/ComponentTypeYamlEditor/ComponentTypeYamlEditorExtension.tsx diff --git a/packages/portal-app/src/scaffolder/ComponentTypeYamlEditor/styles.ts b/plugins/openchoreo/src/scaffolder/ComponentTypeYamlEditor/styles.ts similarity index 100% rename from packages/portal-app/src/scaffolder/ComponentTypeYamlEditor/styles.ts rename to plugins/openchoreo/src/scaffolder/ComponentTypeYamlEditor/styles.ts diff --git a/packages/portal-app/src/scaffolder/ComponentWorkflowYamlEditor/ComponentWorkflowYamlEditorExtension.tsx b/plugins/openchoreo/src/scaffolder/ComponentWorkflowYamlEditor/ComponentWorkflowYamlEditorExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ComponentWorkflowYamlEditor/ComponentWorkflowYamlEditorExtension.tsx rename to plugins/openchoreo/src/scaffolder/ComponentWorkflowYamlEditor/ComponentWorkflowYamlEditorExtension.tsx diff --git a/packages/portal-app/src/scaffolder/ComponentWorkflowYamlEditor/styles.ts b/plugins/openchoreo/src/scaffolder/ComponentWorkflowYamlEditor/styles.ts similarity index 100% rename from packages/portal-app/src/scaffolder/ComponentWorkflowYamlEditor/styles.ts rename to plugins/openchoreo/src/scaffolder/ComponentWorkflowYamlEditor/styles.ts diff --git a/packages/portal-app/src/scaffolder/ContainerImageField/ContainerImageField.tsx b/plugins/openchoreo/src/scaffolder/ContainerImageField/ContainerImageField.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ContainerImageField/ContainerImageField.tsx rename to plugins/openchoreo/src/scaffolder/ContainerImageField/ContainerImageField.tsx diff --git a/packages/portal-app/src/scaffolder/DeploymentPipelineFormWithYaml/DeploymentPipelineFormWithYamlExtension.tsx b/plugins/openchoreo/src/scaffolder/DeploymentPipelineFormWithYaml/DeploymentPipelineFormWithYamlExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/DeploymentPipelineFormWithYaml/DeploymentPipelineFormWithYamlExtension.tsx rename to plugins/openchoreo/src/scaffolder/DeploymentPipelineFormWithYaml/DeploymentPipelineFormWithYamlExtension.tsx diff --git a/packages/portal-app/src/scaffolder/DeploymentPipelineFormWithYaml/styles.ts b/plugins/openchoreo/src/scaffolder/DeploymentPipelineFormWithYaml/styles.ts similarity index 100% rename from packages/portal-app/src/scaffolder/DeploymentPipelineFormWithYaml/styles.ts rename to plugins/openchoreo/src/scaffolder/DeploymentPipelineFormWithYaml/styles.ts diff --git a/packages/portal-app/src/scaffolder/DeploymentPipelinePicker/DeploymentPipelinePickerExtension.tsx b/plugins/openchoreo/src/scaffolder/DeploymentPipelinePicker/DeploymentPipelinePickerExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/DeploymentPipelinePicker/DeploymentPipelinePickerExtension.tsx rename to plugins/openchoreo/src/scaffolder/DeploymentPipelinePicker/DeploymentPipelinePickerExtension.tsx diff --git a/packages/portal-app/src/scaffolder/DeploymentSourcePicker/DeploymentSourcePicker.tsx b/plugins/openchoreo/src/scaffolder/DeploymentSourcePicker/DeploymentSourcePicker.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/DeploymentSourcePicker/DeploymentSourcePicker.tsx rename to plugins/openchoreo/src/scaffolder/DeploymentSourcePicker/DeploymentSourcePicker.tsx diff --git a/packages/portal-app/src/scaffolder/EnvironmentFormWithYaml/EnvironmentFormWithYamlExtension.tsx b/plugins/openchoreo/src/scaffolder/EnvironmentFormWithYaml/EnvironmentFormWithYamlExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/EnvironmentFormWithYaml/EnvironmentFormWithYamlExtension.tsx rename to plugins/openchoreo/src/scaffolder/EnvironmentFormWithYaml/EnvironmentFormWithYamlExtension.tsx diff --git a/packages/portal-app/src/scaffolder/EnvironmentFormWithYaml/styles.ts b/plugins/openchoreo/src/scaffolder/EnvironmentFormWithYaml/styles.ts similarity index 100% rename from packages/portal-app/src/scaffolder/EnvironmentFormWithYaml/styles.ts rename to plugins/openchoreo/src/scaffolder/EnvironmentFormWithYaml/styles.ts diff --git a/packages/portal-app/src/scaffolder/GitSecretField/GitSecretDialog.tsx b/plugins/openchoreo/src/scaffolder/GitSecretField/GitSecretDialog.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/GitSecretField/GitSecretDialog.tsx rename to plugins/openchoreo/src/scaffolder/GitSecretField/GitSecretDialog.tsx diff --git a/packages/portal-app/src/scaffolder/GitSourceField/GitSourceField.tsx b/plugins/openchoreo/src/scaffolder/GitSourceField/GitSourceField.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/GitSourceField/GitSourceField.tsx rename to plugins/openchoreo/src/scaffolder/GitSourceField/GitSourceField.tsx diff --git a/packages/portal-app/src/scaffolder/NamespaceEntityPicker/NamespaceEntityPicker.tsx b/plugins/openchoreo/src/scaffolder/NamespaceEntityPicker/NamespaceEntityPicker.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/NamespaceEntityPicker/NamespaceEntityPicker.tsx rename to plugins/openchoreo/src/scaffolder/NamespaceEntityPicker/NamespaceEntityPicker.tsx diff --git a/packages/portal-app/src/scaffolder/NamespaceEntityPicker/NamespaceSelectField.tsx b/plugins/openchoreo/src/scaffolder/NamespaceEntityPicker/NamespaceSelectField.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/NamespaceEntityPicker/NamespaceSelectField.tsx rename to plugins/openchoreo/src/scaffolder/NamespaceEntityPicker/NamespaceSelectField.tsx diff --git a/packages/portal-app/src/scaffolder/NamespaceEntityPicker/index.ts b/plugins/openchoreo/src/scaffolder/NamespaceEntityPicker/index.ts similarity index 64% rename from packages/portal-app/src/scaffolder/NamespaceEntityPicker/index.ts rename to plugins/openchoreo/src/scaffolder/NamespaceEntityPicker/index.ts index 3e173cee3..1b2762d06 100644 --- a/packages/portal-app/src/scaffolder/NamespaceEntityPicker/index.ts +++ b/plugins/openchoreo/src/scaffolder/NamespaceEntityPicker/index.ts @@ -1,4 +1,3 @@ -export { NamespaceEntityPickerFieldExtension } from './extensions'; export { NamespaceSelectField, type NamespaceSelectFieldProps, diff --git a/packages/portal-app/src/scaffolder/NotificationChannelFormWithYaml/NotificationChannelFormWithYamlExtension.test.tsx b/plugins/openchoreo/src/scaffolder/NotificationChannelFormWithYaml/NotificationChannelFormWithYamlExtension.test.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/NotificationChannelFormWithYaml/NotificationChannelFormWithYamlExtension.test.tsx rename to plugins/openchoreo/src/scaffolder/NotificationChannelFormWithYaml/NotificationChannelFormWithYamlExtension.test.tsx diff --git a/packages/portal-app/src/scaffolder/NotificationChannelFormWithYaml/NotificationChannelFormWithYamlExtension.tsx b/plugins/openchoreo/src/scaffolder/NotificationChannelFormWithYaml/NotificationChannelFormWithYamlExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/NotificationChannelFormWithYaml/NotificationChannelFormWithYamlExtension.tsx rename to plugins/openchoreo/src/scaffolder/NotificationChannelFormWithYaml/NotificationChannelFormWithYamlExtension.tsx diff --git a/packages/portal-app/src/scaffolder/NotificationChannelFormWithYaml/styles.ts b/plugins/openchoreo/src/scaffolder/NotificationChannelFormWithYaml/styles.ts similarity index 100% rename from packages/portal-app/src/scaffolder/NotificationChannelFormWithYaml/styles.ts rename to plugins/openchoreo/src/scaffolder/NotificationChannelFormWithYaml/styles.ts diff --git a/packages/portal-app/src/scaffolder/ProjectNamespaceField/ProjectNamespaceField.tsx b/plugins/openchoreo/src/scaffolder/ProjectNamespaceField/ProjectNamespaceField.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ProjectNamespaceField/ProjectNamespaceField.tsx rename to plugins/openchoreo/src/scaffolder/ProjectNamespaceField/ProjectNamespaceField.tsx diff --git a/packages/portal-app/src/scaffolder/ProjectNamespaceField/projectNamespaceFieldValidation.test.ts b/plugins/openchoreo/src/scaffolder/ProjectNamespaceField/projectNamespaceFieldValidation.test.ts similarity index 100% rename from packages/portal-app/src/scaffolder/ProjectNamespaceField/projectNamespaceFieldValidation.test.ts rename to plugins/openchoreo/src/scaffolder/ProjectNamespaceField/projectNamespaceFieldValidation.test.ts diff --git a/packages/portal-app/src/scaffolder/ProjectParametersField/ProjectParametersField.test.tsx b/plugins/openchoreo/src/scaffolder/ProjectParametersField/ProjectParametersField.test.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ProjectParametersField/ProjectParametersField.test.tsx rename to plugins/openchoreo/src/scaffolder/ProjectParametersField/ProjectParametersField.test.tsx diff --git a/packages/portal-app/src/scaffolder/ProjectParametersField/ProjectParametersField.tsx b/plugins/openchoreo/src/scaffolder/ProjectParametersField/ProjectParametersField.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ProjectParametersField/ProjectParametersField.tsx rename to plugins/openchoreo/src/scaffolder/ProjectParametersField/ProjectParametersField.tsx diff --git a/packages/portal-app/src/scaffolder/ProjectTypeYamlEditor/ProjectTypeYamlEditorExtension.test.tsx b/plugins/openchoreo/src/scaffolder/ProjectTypeYamlEditor/ProjectTypeYamlEditorExtension.test.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ProjectTypeYamlEditor/ProjectTypeYamlEditorExtension.test.tsx rename to plugins/openchoreo/src/scaffolder/ProjectTypeYamlEditor/ProjectTypeYamlEditorExtension.test.tsx diff --git a/packages/portal-app/src/scaffolder/ProjectTypeYamlEditor/ProjectTypeYamlEditorExtension.tsx b/plugins/openchoreo/src/scaffolder/ProjectTypeYamlEditor/ProjectTypeYamlEditorExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ProjectTypeYamlEditor/ProjectTypeYamlEditorExtension.tsx rename to plugins/openchoreo/src/scaffolder/ProjectTypeYamlEditor/ProjectTypeYamlEditorExtension.tsx diff --git a/packages/portal-app/src/scaffolder/ProjectTypeYamlEditor/styles.ts b/plugins/openchoreo/src/scaffolder/ProjectTypeYamlEditor/styles.ts similarity index 100% rename from packages/portal-app/src/scaffolder/ProjectTypeYamlEditor/styles.ts rename to plugins/openchoreo/src/scaffolder/ProjectTypeYamlEditor/styles.ts diff --git a/packages/portal-app/src/scaffolder/ResourceNamePicker/ResourceNamePickerExtension.tsx b/plugins/openchoreo/src/scaffolder/ResourceNamePicker/ResourceNamePickerExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ResourceNamePicker/ResourceNamePickerExtension.tsx rename to plugins/openchoreo/src/scaffolder/ResourceNamePicker/ResourceNamePickerExtension.tsx diff --git a/packages/portal-app/src/scaffolder/ResourceParametersField/ResourceParametersField.tsx b/plugins/openchoreo/src/scaffolder/ResourceParametersField/ResourceParametersField.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ResourceParametersField/ResourceParametersField.tsx rename to plugins/openchoreo/src/scaffolder/ResourceParametersField/ResourceParametersField.tsx diff --git a/packages/portal-app/src/scaffolder/ResourceTypeYamlEditor/ResourceTypeYamlEditorExtension.tsx b/plugins/openchoreo/src/scaffolder/ResourceTypeYamlEditor/ResourceTypeYamlEditorExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ResourceTypeYamlEditor/ResourceTypeYamlEditorExtension.tsx rename to plugins/openchoreo/src/scaffolder/ResourceTypeYamlEditor/ResourceTypeYamlEditorExtension.tsx diff --git a/packages/portal-app/src/scaffolder/ResourceTypeYamlEditor/styles.ts b/plugins/openchoreo/src/scaffolder/ResourceTypeYamlEditor/styles.ts similarity index 100% rename from packages/portal-app/src/scaffolder/ResourceTypeYamlEditor/styles.ts rename to plugins/openchoreo/src/scaffolder/ResourceTypeYamlEditor/styles.ts diff --git a/packages/portal-app/src/scaffolder/ScaffolderPreselectionContext.tsx b/plugins/openchoreo/src/scaffolder/ScaffolderPreselectionContext.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/ScaffolderPreselectionContext.tsx rename to plugins/openchoreo/src/scaffolder/ScaffolderPreselectionContext.tsx diff --git a/packages/portal-app/src/scaffolder/SwitchField/SwitchFieldExtension.test.tsx b/plugins/openchoreo/src/scaffolder/SwitchField/SwitchFieldExtension.test.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/SwitchField/SwitchFieldExtension.test.tsx rename to plugins/openchoreo/src/scaffolder/SwitchField/SwitchFieldExtension.test.tsx diff --git a/packages/portal-app/src/scaffolder/SwitchField/SwitchFieldExtension.tsx b/plugins/openchoreo/src/scaffolder/SwitchField/SwitchFieldExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/SwitchField/SwitchFieldExtension.tsx rename to plugins/openchoreo/src/scaffolder/SwitchField/SwitchFieldExtension.tsx diff --git a/packages/portal-app/src/scaffolder/TraitYamlEditor/TraitYamlEditorExtension.tsx b/plugins/openchoreo/src/scaffolder/TraitYamlEditor/TraitYamlEditorExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/TraitYamlEditor/TraitYamlEditorExtension.tsx rename to plugins/openchoreo/src/scaffolder/TraitYamlEditor/TraitYamlEditorExtension.tsx diff --git a/packages/portal-app/src/scaffolder/TraitYamlEditor/styles.ts b/plugins/openchoreo/src/scaffolder/TraitYamlEditor/styles.ts similarity index 100% rename from packages/portal-app/src/scaffolder/TraitYamlEditor/styles.ts rename to plugins/openchoreo/src/scaffolder/TraitYamlEditor/styles.ts diff --git a/packages/portal-app/src/scaffolder/TraitsField/NoTraitsAvailableMessage.tsx b/plugins/openchoreo/src/scaffolder/TraitsField/NoTraitsAvailableMessage.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/TraitsField/NoTraitsAvailableMessage.tsx rename to plugins/openchoreo/src/scaffolder/TraitsField/NoTraitsAvailableMessage.tsx diff --git a/packages/portal-app/src/scaffolder/TraitsField/TraitCard.tsx b/plugins/openchoreo/src/scaffolder/TraitsField/TraitCard.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/TraitsField/TraitCard.tsx rename to plugins/openchoreo/src/scaffolder/TraitsField/TraitCard.tsx diff --git a/packages/portal-app/src/scaffolder/TraitsField/TraitPicker.tsx b/plugins/openchoreo/src/scaffolder/TraitsField/TraitPicker.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/TraitsField/TraitPicker.tsx rename to plugins/openchoreo/src/scaffolder/TraitsField/TraitPicker.tsx diff --git a/packages/portal-app/src/scaffolder/TraitsField/TraitsFieldExtension.tsx b/plugins/openchoreo/src/scaffolder/TraitsField/TraitsFieldExtension.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/TraitsField/TraitsFieldExtension.tsx rename to plugins/openchoreo/src/scaffolder/TraitsField/TraitsFieldExtension.tsx diff --git a/packages/portal-app/src/scaffolder/WorkloadDetailsField/WorkloadDetailsField.tsx b/plugins/openchoreo/src/scaffolder/WorkloadDetailsField/WorkloadDetailsField.tsx similarity index 99% rename from packages/portal-app/src/scaffolder/WorkloadDetailsField/WorkloadDetailsField.tsx rename to plugins/openchoreo/src/scaffolder/WorkloadDetailsField/WorkloadDetailsField.tsx index fae0009ab..11aef09dd 100644 --- a/packages/portal-app/src/scaffolder/WorkloadDetailsField/WorkloadDetailsField.tsx +++ b/plugins/openchoreo/src/scaffolder/WorkloadDetailsField/WorkloadDetailsField.tsx @@ -49,7 +49,7 @@ import type { FileVar, Container, } from '@openchoreo/backstage-plugin-common'; -import { openChoreoClientApiRef } from '@openchoreo/backstage-plugin'; +import { openChoreoClientApiRef } from '../../api/OpenChoreoClientApi'; const CONTAINER_NAME = 'main'; diff --git a/plugins/openchoreo/src/scaffolder/extensions.ts b/plugins/openchoreo/src/scaffolder/extensions.ts new file mode 100644 index 000000000..0f751df5d --- /dev/null +++ b/plugins/openchoreo/src/scaffolder/extensions.ts @@ -0,0 +1,323 @@ +import { makeFormFieldExtension } from './makeFormFieldExtension'; + +// Single source of truth for OpenChoreo scaffolder field extensions. +// Blueprint names here MUST stay in lockstep with the entries listed in +// plugins/openchoreo/src/alpha.test.tsx (ALPHA_EXTENSION_NAMES). +export const scaffolderFieldExtensions = [ + makeFormFieldExtension( + 'advanced-configuration-field', + () => import('./AdvancedConfigurationField/AdvancedConfigurationField'), + m => ({ + name: 'AdvancedConfigurationField', + component: m.AdvancedConfigurationField, + schema: m.AdvancedConfigurationFieldSchema, + validation: m.advancedConfigurationFieldValidation, + }), + ), + makeFormFieldExtension( + 'build-and-deploy-field', + () => import('./BuildAndDeployField/BuildAndDeployField'), + m => ({ + name: 'BuildAndDeployField', + component: m.BuildAndDeployField, + schema: m.BuildAndDeployFieldSchema, + }), + ), + makeFormFieldExtension( + 'build-template-parameters', + () => import('./BuildTemplateParameters/BuildTemplateParametersExtension'), + m => ({ + name: 'BuildTemplateParameters', + component: m.BuildTemplateParameters, + validation: m.buildTemplateParametersValidation, + }), + ), + makeFormFieldExtension( + 'build-template-picker', + () => import('./BuildTemplatePicker/BuildTemplatePickerExtension'), + m => ({ + name: 'BuildTemplatePicker', + component: m.BuildTemplatePicker, + validation: m.buildTemplatePickerValidation, + }), + ), + makeFormFieldExtension( + 'build-workflow-parameters', + () => import('./BuildWorkflowParameters/BuildWorkflowParametersExtension'), + m => ({ + name: 'BuildWorkflowParameters', + component: m.BuildWorkflowParameters, + schema: m.BuildWorkflowParametersSchema, + validation: m.buildWorkflowParametersValidation, + }), + ), + makeFormFieldExtension( + 'build-workflow-picker', + () => import('./BuildWorkflowPicker/BuildWorkflowPickerExtension'), + m => ({ + name: 'BuildWorkflowPicker', + component: m.BuildWorkflowPicker, + schema: m.BuildWorkflowPickerSchema, + validation: m.buildWorkflowPickerValidation, + }), + ), + makeFormFieldExtension( + 'cluster-component-type-yaml-editor', + () => + import( + './ClusterComponentTypeYamlEditor/ClusterComponentTypeYamlEditorExtension' + ), + m => ({ + name: 'ClusterComponentTypeYamlEditor', + component: m.ClusterComponentTypeYamlEditorExtension, + validation: m.clusterComponentTypeYamlEditorValidation, + }), + ), + makeFormFieldExtension( + 'cluster-project-type-yaml-editor', + () => + import( + './ClusterProjectTypeYamlEditor/ClusterProjectTypeYamlEditorExtension' + ), + m => ({ + name: 'ClusterProjectTypeYamlEditor', + component: m.ClusterProjectTypeYamlEditorExtension, + validation: m.clusterProjectTypeYamlEditorValidation, + }), + ), + makeFormFieldExtension( + 'cluster-resource-type-yaml-editor', + () => + import( + './ClusterResourceTypeYamlEditor/ClusterResourceTypeYamlEditorExtension' + ), + m => ({ + name: 'ClusterResourceTypeYamlEditor', + component: m.ClusterResourceTypeYamlEditorExtension, + validation: m.clusterResourceTypeYamlEditorValidation, + }), + ), + makeFormFieldExtension( + 'cluster-trait-yaml-editor', + () => import('./ClusterTraitYamlEditor/ClusterTraitYamlEditorExtension'), + m => ({ + name: 'ClusterTraitYamlEditor', + component: m.ClusterTraitYamlEditorExtension, + validation: m.clusterTraitYamlEditorValidation, + }), + ), + makeFormFieldExtension( + 'cluster-workflow-yaml-editor', + () => + import('./ClusterWorkflowYamlEditor/ClusterWorkflowYamlEditorExtension'), + m => ({ + name: 'ClusterWorkflowYamlEditor', + component: m.ClusterWorkflowYamlEditorExtension, + validation: m.clusterWorkflowYamlEditorValidation, + }), + ), + makeFormFieldExtension( + 'component-name-picker', + () => import('./ComponentNamePicker/ComponentNamePickerExtension'), + m => ({ + name: 'ComponentNamePicker', + component: m.ComponentNamePicker, + validation: m.componentNamePickerValidation, + }), + ), + makeFormFieldExtension( + 'component-type-yaml-editor', + () => import('./ComponentTypeYamlEditor/ComponentTypeYamlEditorExtension'), + m => ({ + name: 'ComponentTypeYamlEditor', + component: m.ComponentTypeYamlEditorExtension, + validation: m.componentTypeYamlEditorValidation, + }), + ), + makeFormFieldExtension( + 'component-workflow-yaml-editor', + () => + import( + './ComponentWorkflowYamlEditor/ComponentWorkflowYamlEditorExtension' + ), + m => ({ + name: 'ComponentWorkflowYamlEditor', + component: m.ComponentWorkflowYamlEditorExtension, + validation: m.componentWorkflowYamlEditorValidation, + }), + ), + makeFormFieldExtension( + 'container-image-field', + () => import('./ContainerImageField/ContainerImageField'), + m => ({ + name: 'ContainerImageField', + component: m.ContainerImageField, + schema: m.ContainerImageFieldSchema, + validation: m.containerImageFieldValidation, + }), + ), + makeFormFieldExtension( + 'deployment-pipeline-form-with-yaml', + () => + import( + './DeploymentPipelineFormWithYaml/DeploymentPipelineFormWithYamlExtension' + ), + m => ({ + name: 'DeploymentPipelineFormWithYaml', + component: m.DeploymentPipelineFormWithYamlExtension, + schema: m.DeploymentPipelineFormWithYamlSchema, + validation: m.deploymentPipelineFormWithYamlValidation, + }), + ), + makeFormFieldExtension( + 'deployment-pipeline-picker', + () => + import('./DeploymentPipelinePicker/DeploymentPipelinePickerExtension'), + m => ({ + name: 'DeploymentPipelinePicker', + component: m.DeploymentPipelinePicker, + }), + ), + makeFormFieldExtension( + 'deployment-source-picker', + () => import('./DeploymentSourcePicker/DeploymentSourcePicker'), + m => ({ + name: 'DeploymentSourcePicker', + component: m.DeploymentSourcePicker, + schema: m.DeploymentSourcePickerSchema, + }), + ), + makeFormFieldExtension( + 'environment-form-with-yaml', + () => import('./EnvironmentFormWithYaml/EnvironmentFormWithYamlExtension'), + m => ({ + name: 'EnvironmentFormWithYaml', + component: m.EnvironmentFormWithYamlExtension, + schema: m.EnvironmentFormWithYamlSchema, + validation: m.environmentFormWithYamlValidation, + }), + ), + makeFormFieldExtension( + 'git-source-field', + () => import('./GitSourceField/GitSourceField'), + m => ({ + name: 'GitSourceField', + component: m.GitSourceField, + schema: m.GitSourceFieldSchema, + validation: m.gitSourceFieldValidation, + }), + ), + makeFormFieldExtension( + 'namespace-entity-picker', + () => import('./NamespaceEntityPicker/NamespaceEntityPicker'), + m => ({ + name: 'NamespaceEntityPicker', + component: m.NamespaceEntityPicker, + }), + ), + makeFormFieldExtension( + 'notification-channel-form-with-yaml', + () => + import( + './NotificationChannelFormWithYaml/NotificationChannelFormWithYamlExtension' + ), + m => ({ + name: 'NotificationChannelFormWithYaml', + component: m.NotificationChannelFormWithYamlExtension, + schema: m.NotificationChannelFormWithYamlSchema, + validation: m.notificationChannelFormWithYamlValidation, + }), + ), + makeFormFieldExtension( + 'project-namespace-field', + () => import('./ProjectNamespaceField/ProjectNamespaceField'), + m => ({ + name: 'ProjectNamespaceField', + component: m.ProjectNamespaceField, + schema: m.ProjectNamespaceFieldSchema, + validation: m.projectNamespaceFieldValidation, + }), + ), + makeFormFieldExtension( + 'project-parameters-field', + () => import('./ProjectParametersField/ProjectParametersField'), + m => ({ + name: 'ProjectParametersField', + component: m.ProjectParametersField, + schema: m.ProjectParametersFieldSchema, + }), + ), + makeFormFieldExtension( + 'project-type-yaml-editor', + () => import('./ProjectTypeYamlEditor/ProjectTypeYamlEditorExtension'), + m => ({ + name: 'ProjectTypeYamlEditor', + component: m.ProjectTypeYamlEditorExtension, + validation: m.projectTypeYamlEditorValidation, + }), + ), + makeFormFieldExtension( + 'resource-name-picker', + () => import('./ResourceNamePicker/ResourceNamePickerExtension'), + m => ({ + name: 'ResourceNamePicker', + component: m.ResourceNamePicker, + validation: m.resourceNamePickerValidation, + }), + ), + makeFormFieldExtension( + 'resource-parameters-field', + () => import('./ResourceParametersField/ResourceParametersField'), + m => ({ + name: 'ResourceParametersField', + component: m.ResourceParametersField, + schema: m.ResourceParametersFieldSchema, + }), + ), + makeFormFieldExtension( + 'resource-type-yaml-editor', + () => import('./ResourceTypeYamlEditor/ResourceTypeYamlEditorExtension'), + m => ({ + name: 'ResourceTypeYamlEditor', + component: m.ResourceTypeYamlEditorExtension, + validation: m.resourceTypeYamlEditorValidation, + }), + ), + makeFormFieldExtension( + 'switch-field', + () => import('./SwitchField/SwitchFieldExtension'), + m => ({ + name: 'SwitchField', + component: m.SwitchField, + schema: m.SwitchFieldSchema, + }), + ), + makeFormFieldExtension( + 'trait-yaml-editor', + () => import('./TraitYamlEditor/TraitYamlEditorExtension'), + m => ({ + name: 'TraitYamlEditor', + component: m.TraitYamlEditorExtension, + validation: m.traitYamlEditorValidation, + }), + ), + makeFormFieldExtension( + 'traits-field', + () => import('./TraitsField/TraitsFieldExtension'), + m => ({ + name: 'TraitsField', + component: m.TraitsField, + validation: m.traitsFieldValidation, + }), + ), + makeFormFieldExtension( + 'workload-details-field', + () => import('./WorkloadDetailsField/WorkloadDetailsField'), + m => ({ + name: 'WorkloadDetailsField', + component: m.WorkloadDetailsField, + schema: m.WorkloadDetailsFieldSchema, + validation: m.workloadDetailsFieldValidation, + }), + ), +]; diff --git a/plugins/openchoreo/src/scaffolder/makeFormFieldExtension.ts b/plugins/openchoreo/src/scaffolder/makeFormFieldExtension.ts new file mode 100644 index 000000000..d1e06a902 --- /dev/null +++ b/plugins/openchoreo/src/scaffolder/makeFormFieldExtension.ts @@ -0,0 +1,24 @@ +import { + FormFieldBlueprint, + createFormField, +} from '@backstage/plugin-scaffolder-react/alpha'; + +type CreateFormFieldParams = Parameters[0]; +type FieldModule = Record; + +// Wraps the FormFieldBlueprint + createFormField + dynamic-import boilerplate +// that every OpenChoreo scaffolder field shares. Each field only needs to +// supply its kebab-case blueprint name, the module loader, and a picker that +// extracts the component (and optional schema / validation) from the module. +export function makeFormFieldExtension( + blueprintName: string, + loader: () => Promise, + build: (mod: FieldModule) => CreateFormFieldParams, +) { + return FormFieldBlueprint.make({ + name: blueprintName, + params: { + field: () => loader().then(m => createFormField(build(m))), + }, + }); +} diff --git a/packages/portal-app/src/scaffolder/types.ts b/plugins/openchoreo/src/scaffolder/types.ts similarity index 100% rename from packages/portal-app/src/scaffolder/types.ts rename to plugins/openchoreo/src/scaffolder/types.ts diff --git a/packages/portal-app/src/scaffolder/utils/rjsfUtils.test.ts b/plugins/openchoreo/src/scaffolder/utils/rjsfUtils.test.ts similarity index 100% rename from packages/portal-app/src/scaffolder/utils/rjsfUtils.test.ts rename to plugins/openchoreo/src/scaffolder/utils/rjsfUtils.test.ts diff --git a/packages/portal-app/src/scaffolder/utils/rjsfUtils.tsx b/plugins/openchoreo/src/scaffolder/utils/rjsfUtils.tsx similarity index 100% rename from packages/portal-app/src/scaffolder/utils/rjsfUtils.tsx rename to plugins/openchoreo/src/scaffolder/utils/rjsfUtils.tsx diff --git a/plugins/platform-engineer-core/README.md b/plugins/platform-engineer-core/README.md index d2ed1ac8c..34dc5523f 100644 --- a/plugins/platform-engineer-core/README.md +++ b/plugins/platform-engineer-core/README.md @@ -2,6 +2,12 @@ This is the frontend plugin for the Platform Engineer Core, which provides a comprehensive view of all environments across the platform for platform engineers. +## Requirements + +Requires [`@openchoreo/backstage-plugin`](../openchoreo) (the OpenChoreo +core plugin) to be installed in the same Backstage app. The core plugin +provides the fetch / permission / auth APIs this plugin uses. + ## Features - View all environments across organizations in a card-based layout diff --git a/plugins/platform-engineer-core/package.json b/plugins/platform-engineer-core/package.json index 1ac7338f9..1b996fa4a 100644 --- a/plugins/platform-engineer-core/package.json +++ b/plugins/platform-engineer-core/package.json @@ -67,6 +67,7 @@ "react-use": "17.6.0" }, "peerDependencies": { + "@openchoreo/backstage-plugin": "^1.2.0", "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^6.0.0" diff --git a/plugins/platform-engineer-core/src/alpha.tsx b/plugins/platform-engineer-core/src/alpha.tsx index 321f5a0a3..7221b6bf5 100644 --- a/plugins/platform-engineer-core/src/alpha.tsx +++ b/plugins/platform-engineer-core/src/alpha.tsx @@ -1,10 +1,11 @@ +import BubbleChartIcon from '@material-ui/icons/BubbleChart'; import { createFrontendPlugin, PageBlueprint, PluginWrapperBlueprint, } from '@backstage/frontend-plugin-api'; -import { rootRouteRef } from './routes'; +import { platformOverviewRouteRef, rootRouteRef } from './routes'; const platformEngineerDashboardPage = PageBlueprint.make({ name: 'platform-engineer-dashboard', @@ -18,9 +19,24 @@ const platformEngineerDashboardPage = PageBlueprint.make({ }, }); -// Wraps this plugin's own extensions in the TanStack Query provider (see the -// openchoreo plugin's alpha for the full rationale). Shares the one `queryClient` -// singleton across all OpenChoreo plugins. +// Ships title + icon so adopters auto-get a sidebar entry via DefaultNavContent. +const platformOverviewPage = PageBlueprint.make({ + name: 'platform-overview', + params: { + path: '/platform-overview', + routeRef: platformOverviewRouteRef, + title: 'Platform', + icon: , + // Page renders its own
; suppress outer PageLayout header. + noHeader: true, + loader: () => + import('./components/PlatformOverviewPage').then(m => ( + + )), + }, +}); + +// Wraps this plugin's extensions in the shared OpenChoreoQueryProvider. const queryProvider = PluginWrapperBlueprint.make({ name: 'query-provider', params: defineParams => @@ -34,11 +50,12 @@ const queryProvider = PluginWrapperBlueprint.make({ }), }); -/** - * NFS entry point for the Platform Engineer Core plugin. - */ export default createFrontendPlugin({ pluginId: 'platform-engineer-core', routes: { root: rootRouteRef }, - extensions: [queryProvider, platformEngineerDashboardPage], + extensions: [ + queryProvider, + platformEngineerDashboardPage, + platformOverviewPage, + ], }); diff --git a/packages/portal-app/src/components/platformOverview/PlatformOverviewPage.test.tsx b/plugins/platform-engineer-core/src/components/PlatformOverviewPage/PlatformOverviewPage.test.tsx similarity index 100% rename from packages/portal-app/src/components/platformOverview/PlatformOverviewPage.test.tsx rename to plugins/platform-engineer-core/src/components/PlatformOverviewPage/PlatformOverviewPage.test.tsx diff --git a/packages/portal-app/src/components/platformOverview/PlatformOverviewPage.tsx b/plugins/platform-engineer-core/src/components/PlatformOverviewPage/PlatformOverviewPage.tsx similarity index 99% rename from packages/portal-app/src/components/platformOverview/PlatformOverviewPage.tsx rename to plugins/platform-engineer-core/src/components/PlatformOverviewPage/PlatformOverviewPage.tsx index 894ac2742..4a20b7ede 100644 --- a/packages/portal-app/src/components/platformOverview/PlatformOverviewPage.tsx +++ b/plugins/platform-engineer-core/src/components/PlatformOverviewPage/PlatformOverviewPage.tsx @@ -30,10 +30,10 @@ import { CLUSTER_NAMESPACE, CLUSTER_SCOPED_KINDS, useProjects, + useQueryParams, type ProjectEntry, type EntityNode, } from '@openchoreo/backstage-plugin-react'; -import { useQueryParams } from '@openchoreo/backstage-plugin'; const useStyles = makeStyles(theme => ({ content: { diff --git a/packages/portal-app/src/components/platformOverview/index.ts b/plugins/platform-engineer-core/src/components/PlatformOverviewPage/index.ts similarity index 100% rename from packages/portal-app/src/components/platformOverview/index.ts rename to plugins/platform-engineer-core/src/components/PlatformOverviewPage/index.ts diff --git a/plugins/platform-engineer-core/src/components/index.ts b/plugins/platform-engineer-core/src/components/index.ts index 73e096011..9d6fed317 100644 --- a/plugins/platform-engineer-core/src/components/index.ts +++ b/plugins/platform-engineer-core/src/components/index.ts @@ -3,3 +3,4 @@ export { DeveloperPortalWidget } from './DeveloperPortalWidget'; export { AgentHealthWidget } from './AgentHealthWidget'; export { PlatformDetailsCard } from './PlatformDetailsCard'; export { HomePagePlatformDetailsCard } from './HomePagePlatformDetailsCard'; +export { PlatformOverviewPage } from './PlatformOverviewPage'; diff --git a/plugins/platform-engineer-core/src/routes.ts b/plugins/platform-engineer-core/src/routes.ts index 4393e28a3..cb8d0d85b 100644 --- a/plugins/platform-engineer-core/src/routes.ts +++ b/plugins/platform-engineer-core/src/routes.ts @@ -3,3 +3,8 @@ import { createRouteRef } from '@backstage/core-plugin-api'; export const rootRouteRef = createRouteRef({ id: 'platform-engineer-core', }); + +// Separate from rootRouteRef (which is bound to /platform-engineer-view). +export const platformOverviewRouteRef = createRouteRef({ + id: 'platform-engineer-core.platform-overview', +}); diff --git a/yarn.lock b/yarn.lock index 6070d5c74..1bb7fa145 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9909,6 +9909,7 @@ __metadata: react-use: "npm:17.6.0" yaml: "npm:^2.8.1" peerDependencies: + "@openchoreo/backstage-plugin": ^1.2.0 react: ^18.0.0 react-router-dom: ^6.0.0 languageName: unknown @@ -9970,6 +9971,7 @@ __metadata: remark-gfm: "npm:^4.0.1" remend: "npm:^1.0.0" peerDependencies: + "@openchoreo/backstage-plugin": ^1.2.0 react: ^18.0.0 react-router-dom: ^6.0.0 languageName: unknown @@ -10016,6 +10018,7 @@ __metadata: remark-gfm: "npm:^4.0.1" remend: "npm:^1.0.0" peerDependencies: + "@openchoreo/backstage-plugin": ^1.2.0 react: 18.0.0 react-router-dom: 6.0.0 languageName: unknown @@ -10070,6 +10073,7 @@ __metadata: react-use: "npm:17.6.0" yaml: "npm:^2.8.2" peerDependencies: + "@openchoreo/backstage-plugin": ^1.2.0 react: ^18.0.0 react-router-dom: ^6.0.0 languageName: unknown @@ -10154,6 +10158,7 @@ __metadata: msw: "npm:1.3.5" react-use: "npm:17.6.0" peerDependencies: + "@openchoreo/backstage-plugin": ^1.2.0 react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.0.0 @@ -10255,6 +10260,7 @@ __metadata: dependencies: "@backstage/catalog-model": "npm:^1.9.0" "@backstage/cli": "npm:^0.36.2" + "@backstage/config": "npm:^1.3.8" "@backstage/core-app-api": "npm:^1.20.1" "@backstage/core-components": "npm:^0.18.10" "@backstage/core-plugin-api": "npm:^1.12.6" @@ -10267,7 +10273,9 @@ __metadata: "@backstage/plugin-catalog-common": "npm:^1.1.10" "@backstage/plugin-catalog-graph": "npm:^0.6.4" "@backstage/plugin-catalog-react": "npm:^3.0.0" + "@backstage/plugin-permission-common": "npm:^0.9.9" "@backstage/plugin-permission-react": "npm:^0.5.1" + "@backstage/plugin-scaffolder-react": "npm:^2.0.0" "@backstage/plugin-techdocs-common": "npm:^0.1.1" "@backstage/test-utils": "npm:^1.7.18" "@backstage/theme": "npm:^0.7.3"