Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
67dfd6f
chore(app): declare NFS frontend-system packages as direct deps
kaviththiranga Jun 9, 2026
50d8c87
feat(openchoreo-ci): add /alpha NFS entry point
kaviththiranga Jun 9, 2026
32a1d08
feat(openchoreo-workflows): add /alpha NFS entry point
kaviththiranga Jun 9, 2026
3845410
feat(platform-engineer-core): add /alpha NFS entry point
kaviththiranga Jun 9, 2026
b17fde4
feat(openchoreo-observability): add /alpha NFS entry point
kaviththiranga Jun 9, 2026
36edb0a
feat(openchoreo): add /alpha NFS entry point
kaviththiranga Jun 9, 2026
8ee4a01
feat(app): switch shell to NFS createApp from @backstage/frontend-def…
kaviththiranga Jun 9, 2026
6d1ff41
feat(app): register custom NFS plugins and upstream scaffolder feature
kaviththiranga Jun 9, 2026
ff0d23b
feat(app): restore custom scaffolder UI under NFS
kaviththiranga Jun 9, 2026
51da9b1
feat(app): restore catalog/scaffolder API overrides via plugin withOv…
kaviththiranga Jun 9, 2026
84deb4b
feat(app): restore catalog-import translations and register its NFS p…
kaviththiranga Jun 9, 2026
cbfe5bc
feat(app): migrate DynamicSignInPage to SignInPageBlueprint extension
kaviththiranga Jun 9, 2026
872f013
chore: add changeset for NFS migration
kaviththiranga Jun 9, 2026
4b13303
fix: formatting
kaviththiranga Jun 9, 2026
f7c7690
fix: missing clusterworkflow in kindIcons
kaviththiranga Jun 9, 2026
cd71282
test(app): drop stale apis registry assertions for NFS-migrated facto…
kaviththiranga Jun 9, 2026
51947bf
feat(observability): expose log-row-action registry API for NFS host …
kaviththiranga Jun 16, 2026
7759c24
fix(openchoreo-ci): scope Build tab to kind:component under NFS
kaviththiranga Jun 16, 2026
634da71
refactor(openchoreo-react): move FeatureGatedContent next to FeatureGate
kaviththiranga Jun 16, 2026
027549b
feat(observability): register component-page entity tabs as NFS bluep…
kaviththiranga Jun 16, 2026
f0aa4eb
feat(observability): register system-page entity tabs as NFS blueprints
kaviththiranga Jun 16, 2026
b5270d5
feat(openchoreo): register entity tabs and platform-kind cards as NFS…
kaviththiranga Jun 16, 2026
87241e1
feat(openchoreo-workflows): register Workflow Runs entity tab as NFS …
kaviththiranga Jun 16, 2026
6d6839c
feat(app): register host overview cards as NFS EntityCardBlueprints
kaviththiranga Jun 16, 2026
bf2ac84
feat(app): own /catalog/.../entity via NFS page:catalog/entity override
kaviththiranga Jun 16, 2026
1cd3e78
Revert "feat(app): own /catalog/.../entity via NFS page:catalog/entit…
kaviththiranga Jun 16, 2026
fa29ba3
fix(app): reorder createApp features so NFS overrides win over legacy…
kaviththiranga Jun 16, 2026
21f47e7
fix(app): override page:catalog loader for CustomCatalogPage and unbl…
kaviththiranga Jun 16, 2026
e68b94c
fix(app): override page:scaffolder loader to render OpenChoreoScaffol…
kaviththiranga Jun 16, 2026
0499eea
fix(scaffolder): preserve inputs.formDecorators in app override
kaviththiranga Jun 16, 2026
3d95132
fix(app): mount DependencyGraphZoomOverrides under Root and restore S…
kaviththiranga Jun 16, 2026
fac11c6
fix(scaffolder): reset preselection state when query params absent
kaviththiranga Jun 16, 2026
f828a2b
chore(app): cleanup stale comments, duplicate kind icons, dead bindRo…
kaviththiranga Jun 16, 2026
4ce9acb
chore: add changeset for NFS migration fixups
kaviththiranga Jun 16, 2026
0cb403d
chore: apply prettier to NFS migration files
kaviththiranga Jun 17, 2026
a79e3ab
test(app): cover customOverrides extension wiring
kaviththiranga Jun 17, 2026
80bd4ae
test(openchoreo-observability): cover LogRowActionRendererApi and alp…
kaviththiranga Jun 17, 2026
60d9a24
test(openchoreo): cover alpha extension registrations
kaviththiranga Jun 17, 2026
933b02f
test(openchoreo-workflows): cover Workflow Runs alpha extension
kaviththiranga Jun 17, 2026
239bb69
fix(app): restore custom entity page via page:catalog/entity override
kaviththiranga Jun 17, 2026
1dde17e
fix: unblock entity page render under page:catalog/entity override
kaviththiranga Jun 18, 2026
9713b2f
fix(app): register apiDocs and kubernetes NFS plugins
kaviththiranga Jun 18, 2026
7b07aa1
fix: merge changelogs for the PR
kaviththiranga Jun 18, 2026
b1deafe
fix(app): drop redundant page header on /catalog and /create
kaviththiranga Jun 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .changeset/migrate-portal-to-nfs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@openchoreo/backstage-plugin': patch
'@openchoreo/backstage-plugin-openchoreo-ci': patch
'@openchoreo/backstage-plugin-openchoreo-observability': patch
'@openchoreo/backstage-plugin-openchoreo-workflows': patch
'@openchoreo/backstage-plugin-platform-engineer-core': patch
'@openchoreo/backstage-plugin-react': patch
---

Add an `/alpha` entry point that exposes each plugin as a `createFrontendPlugin` for use with Backstage's New Frontend System (NFS). The default entry continues to export the legacy `createPlugin` instance so existing host apps keep working unchanged; adopters on NFS can now import `from '@openchoreo/backstage-plugin-<name>/alpha'` and include the plugin directly in `createApp({ features: [...] })`.

The `/alpha` exports register each plugin's API factories (e.g. `openChoreoCiClientApiRef`, `genericWorkflowsClientApiRef`, the three observability backend clients, `openChoreoClientApiRef`) and one top-level page where applicable (`platform-engineer-core`'s dashboard view, `openchoreo-workflows`' generic workflows page, `openchoreo-ci`'s workflows entity tab).

Entity tabs and overview cards that previously lived in the host's `EntityPage.tsx` now ride through each plugin's `/alpha` export as `EntityContentBlueprint` and `EntityCardBlueprint` extensions, with the right kind filters. Adopters on `/alpha` get the full entity-page contributions automatically: the OpenChoreo CI plugin contributes the Build tab (scoped to `kind:component`); the observability plugin contributes the 10 component- and system-page tabs (Logs, Events, Metrics, Alerts, Wirelogs, Traces, Incidents, RCA Reports, Cost Analysis) plus a registry API for host-injected log-row action renderers; the OpenChoreo plugin contributes the Deploy tab, the system Cell Diagram tab, the shared Resource Definition tab, and 30+ overview cards spanning every OpenChoreo platform kind (Environment, DataPlane, WorkflowPlane, ObservabilityPlane, DeploymentPipeline, the ComponentType / ResourceType / TraitType families, and the Workflow family); the generic-workflows plugin contributes the Runs tab on `Workflow` and `ClusterWorkflow` entities of type `Generic`. The react plugin exposes a new `FeatureGatedContent` component so plugin authors can gate routable extensions on the OpenChoreo feature flags without rolling their own empty-state wrapper.

Custom catalog-graph relations, entity-presentation kind icons, and the scaffolder form-decorator override are now actually applied at runtime — the original migration registered them but they were silently overwritten by upstream defaults at startup. The form-decorator override also stops dropping decorators contributed by other plugins.

Adopters still on the default (legacy) export are unaffected. This addresses the body of [openchoreo/openchoreo#3568](https://github.com/openchoreo/openchoreo/issues/3568) — adopters can drop `--legacy` from the `@backstage/create-app` step when installing the plugin suite into an existing Backstage host.
7 changes: 7 additions & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@
"@backstage/cli": "^0.36.2",
"@backstage/config": "^1.3.8",
"@backstage/core-app-api": "^1.20.1",
"@backstage/core-compat-api": "^0.5.11",
"@backstage/core-components": "^0.18.10",
"@backstage/core-plugin-api": "^1.12.6",
"@backstage/frontend-app-api": "^0.16.3",
"@backstage/frontend-defaults": "^0.5.2",
"@backstage/frontend-plugin-api": "^0.17.0",
"@backstage/integration-react": "^1.2.18",
"@backstage/plugin-api-docs": "^0.14.1",
"@backstage/plugin-app": "^0.4.6",
"@backstage/plugin-app-react": "^0.2.3",
"@backstage/plugin-catalog": "^2.0.5",
"@backstage/plugin-catalog-common": "^1.1.10",
"@backstage/plugin-catalog-graph": "^0.6.4",
Expand Down Expand Up @@ -83,6 +89,7 @@
},
"devDependencies": {
"@axe-core/playwright": "^4.11.3",
"@backstage/frontend-test-utils": "^0.6.0",
"@backstage/test-utils": "^1.7.18",
"@playwright/test": "1.56.0",
"@testing-library/dom": "9.3.4",
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { render, waitFor } from '@testing-library/react';
import App from './App';
import app from './App';

describe('App', () => {
it('should render', async () => {
Expand All @@ -19,7 +19,7 @@ describe('App', () => {
] as any,
};

const rendered = render(<App />);
const rendered = render(app);

await waitFor(() => {
expect(rendered.baseElement).toBeInTheDocument();
Expand Down
328 changes: 105 additions & 223 deletions packages/app/src/App.tsx

Large diffs are not rendered by default.

44 changes: 8 additions & 36 deletions packages/app/src/apis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
*
* 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.
*/
import {
AnyApiFactory,
Expand All @@ -18,21 +24,13 @@ import {
import { permissionApiRef } from '@backstage/plugin-permission-react';
import { visitsApiRef } from '@backstage/plugin-home';
import { scmIntegrationsApiRef } from '@backstage/integration-react';
import { catalogGraphApiRef } from '@backstage/plugin-catalog-graph';
import {
openChoreoCiClientApiRef,
OpenChoreoCiClient,
} from '@openchoreo/backstage-plugin-openchoreo-ci';
import {
genericWorkflowsClientApiRef,
GenericWorkflowsClient,
} from '@openchoreo/backstage-plugin-openchoreo-workflows';
import {
perchAgentApiRef,
PerchAgentClient,
} from '@openchoreo/backstage-plugin-openchoreo-portal-assistant';

import { apis, openChoreoAuthApiRef } from './apis';
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.
Expand Down Expand Up @@ -72,32 +70,12 @@ describe('apis registry', () => {
openChoreoAuthApiRef,
visitsApiRef,
storageApiRef,
openChoreoCiClientApiRef,
genericWorkflowsClientApiRef,
perchAgentApiRef,
]) {
expect(ids).toContain(ref.id);
}
});

it('builds the OpenChoreoCiClient via its factory', () => {
const f = findFactory(apis, openChoreoCiClientApiRef);
const instance = invoke(f, {
discoveryApi: stubDiscovery,
fetchApi: stubFetch,
});
expect(instance).toBeInstanceOf(OpenChoreoCiClient);
});

it('builds the GenericWorkflowsClient via its factory', () => {
const f = findFactory(apis, genericWorkflowsClientApiRef);
const instance = invoke(f, {
discoveryApi: stubDiscovery,
fetchApi: stubFetch,
});
expect(instance).toBeInstanceOf(GenericWorkflowsClient);
});

it('builds the PerchAgentClient via its factory', () => {
const f = findFactory(apis, perchAgentApiRef);
const instance = invoke(f, {
Expand All @@ -115,10 +93,4 @@ describe('apis registry', () => {
});
expect(instance).toBeDefined();
});

it('builds the catalog graph api with custom OpenChoreo relations', () => {
const f = findFactory(apis, catalogGraphApiRef);
const instance = invoke(f, {});
expect(instance).toBeDefined();
});
});
157 changes: 0 additions & 157 deletions packages/app/src/apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,7 @@ 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 {
formDecoratorsApiRef,
DefaultScaffolderFormDecoratorsApi,
} from '@backstage/plugin-scaffolder/alpha';
import { openChoreoTokenDecorator } from './scaffolder/openChoreoTokenDecorator';
// Import from separate file to avoid circular dependency with form decorators
import { openChoreoAuthApiRef } from './apis/authRefs';
import {
openChoreoCiClientApiRef,
OpenChoreoCiClient,
} from '@openchoreo/backstage-plugin-openchoreo-ci';
import {
genericWorkflowsClientApiRef,
GenericWorkflowsClient,
} from '@openchoreo/backstage-plugin-openchoreo-workflows';
// 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
Expand All @@ -52,47 +38,6 @@ import {
perchAgentApiRef,
PerchAgentClient,
} from '@openchoreo/backstage-plugin-openchoreo-portal-assistant';
import {
catalogApiRef,
entityPresentationApiRef,
} from '@backstage/plugin-catalog-react';
import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog';
import {
catalogGraphApiRef,
DefaultCatalogGraphApi,
ALL_RELATIONS,
ALL_RELATION_PAIRS,
} from '@backstage/plugin-catalog-graph';
import {
RELATION_DEPLOYS_TO,
RELATION_DEPLOYED_BY,
RELATION_USES_PIPELINE,
RELATION_PIPELINE_USED_BY,
RELATION_HOSTED_ON,
RELATION_HOSTS,
RELATION_OBSERVED_BY,
RELATION_OBSERVES,
RELATION_INSTANCE_OF,
RELATION_HAS_INSTANCE,
RELATION_USES_WORKFLOW,
RELATION_WORKFLOW_USED_BY,
RELATION_BUILDS_ON,
RELATION_BUILDS,
} from '@openchoreo/backstage-plugin-common';
import CloudIcon from '@material-ui/icons/Cloud';
import DnsIcon from '@material-ui/icons/Dns';
import AccountTreeIcon from '@material-ui/icons/AccountTree';
import VisibilityIcon from '@material-ui/icons/Visibility';
import BuildIcon from '@material-ui/icons/Build';
import CategoryIcon from '@material-ui/icons/Category';
import LayersIcon from '@material-ui/icons/Layers';
import StorageIcon from '@material-ui/icons/Storage';
import ExtensionIcon from '@material-ui/icons/Extension';
import PlayCircleOutlineIcon from '@material-ui/icons/PlayCircleOutline';
import SettingsApplicationsIcon from '@material-ui/icons/SettingsApplications';

// Re-export for use by App.tsx and other components
export { openChoreoAuthApiRef };

export const apis: AnyApiFactory[] = [
createApiFactory({
Expand Down Expand Up @@ -179,78 +124,6 @@ export const apis: AnyApiFactory[] = [
factory: deps => UserSettingsStorage.create(deps),
}),

// Form decorators for scaffolder - injects user's OpenChoreo token as a secret
// This enables user-based authorization in scaffolder actions
createApiFactory({
api: formDecoratorsApiRef,
deps: {},
factory: () =>
DefaultScaffolderFormDecoratorsApi.create({
decorators: [openChoreoTokenDecorator],
}),
}),

// OpenChoreo CI client - provides API for workflow/build operations
createApiFactory({
api: openChoreoCiClientApiRef,
deps: {
discoveryApi: discoveryApiRef,
fetchApi: fetchApiRef,
},
factory: ({ discoveryApi, fetchApi }) =>
new OpenChoreoCiClient(discoveryApi, fetchApi),
}),

// Catalog graph API with custom OpenChoreo relations
// Without this, custom relations (deploysTo, hostedOn, instanceOf, etc.)
// won't appear in entity Relations cards or the catalog graph
createApiFactory({
api: catalogGraphApiRef,
deps: {},
factory: () =>
new DefaultCatalogGraphApi({
knownRelations: [
...ALL_RELATIONS,
RELATION_DEPLOYS_TO,
RELATION_DEPLOYED_BY,
RELATION_USES_PIPELINE,
RELATION_PIPELINE_USED_BY,
RELATION_HOSTED_ON,
RELATION_HOSTS,
RELATION_OBSERVED_BY,
RELATION_OBSERVES,
RELATION_INSTANCE_OF,
RELATION_HAS_INSTANCE,
RELATION_USES_WORKFLOW,
RELATION_WORKFLOW_USED_BY,
RELATION_BUILDS_ON,
RELATION_BUILDS,
],
knownRelationPairs: [
...ALL_RELATION_PAIRS,
[RELATION_DEPLOYS_TO, RELATION_DEPLOYED_BY],
[RELATION_USES_PIPELINE, RELATION_PIPELINE_USED_BY],
[RELATION_HOSTED_ON, RELATION_HOSTS],
[RELATION_OBSERVED_BY, RELATION_OBSERVES],
[RELATION_INSTANCE_OF, RELATION_HAS_INSTANCE],
[RELATION_USES_WORKFLOW, RELATION_WORKFLOW_USED_BY],
[RELATION_BUILDS_ON, RELATION_BUILDS],
],
defaultRelationTypes: { exclude: [] },
}),
}),

// Generic Workflows client - provides API for org-level workflow operations
createApiFactory({
api: genericWorkflowsClientApiRef,
deps: {
discoveryApi: discoveryApiRef,
fetchApi: fetchApiRef,
},
factory: ({ discoveryApi, fetchApi }) =>
new GenericWorkflowsClient(discoveryApi, fetchApi),
}),

// Assistant Agent client (Perch). Mirrors the registration on
// openchoreoPerchPlugin.apis — see the import-site comment for why
// both exist.
Expand All @@ -263,34 +136,4 @@ export const apis: AnyApiFactory[] = [
factory: ({ discoveryApi, fetchApi }) =>
new PerchAgentClient({ discoveryApi, fetchApi }),
}),

// Custom EntityPresentationApi with icons for custom entity kinds
// This enables icons for Environment, DataPlane, and DeploymentPipeline in the catalog graph
createApiFactory({
api: entityPresentationApiRef,
deps: { catalogApi: catalogApiRef },
factory: ({ catalogApi }) =>
DefaultEntityPresentationApi.create({
catalogApi,
kindIcons: {
environment: CloudIcon,
dataplane: DnsIcon,
clusterdataplane: DnsIcon,
deploymentpipeline: AccountTreeIcon,
observabilityplane: VisibilityIcon,
clusterobservabilityplane: VisibilityIcon,
workflowplane: BuildIcon,
clusterworkflowplane: BuildIcon,
componenttype: CategoryIcon,
clustercomponenttype: CategoryIcon,
resourcetype: LayersIcon,
clusterresourcetype: LayersIcon,
resource: StorageIcon,
traittype: ExtensionIcon,
clustertraittype: ExtensionIcon,
workflow: PlayCircleOutlineIcon,
componentworkflow: SettingsApplicationsIcon,
},
}),
}),
];
48 changes: 48 additions & 0 deletions packages/app/src/apis/customOverrides.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
catalogGraphPluginAlpha,
catalogPluginAlpha,
customAppModule,
scaffolderPluginAlpha,
} from './customOverrides';

describe('customOverrides', () => {
it('exports a catalog-graph plugin override', () => {
expect(catalogGraphPluginAlpha).toBeDefined();
expect((catalogGraphPluginAlpha as any).id).toBe('catalog-graph');
});

it('exports a catalog plugin override', () => {
expect(catalogPluginAlpha).toBeDefined();
expect((catalogPluginAlpha as any).id).toBe('catalog');
});

it('exports a scaffolder plugin override', () => {
expect(scaffolderPluginAlpha).toBeDefined();
expect((scaffolderPluginAlpha as any).id).toBe('scaffolder');
});

it('exports the customAppModule frontend module', () => {
expect(customAppModule).toBeDefined();
// `createFrontendModule({ pluginId: 'app', ... })` produces a frontend
// module bound to the `app` plugin id.
expect(
(customAppModule as any).id ?? (customAppModule as any).pluginId,
).toBe('app');
});

it('registers extensions on the customAppModule (SignInPage, Translation, LogRowAction)', () => {
const extensions = ((customAppModule as any).extensions ?? []) as Array<{
id: string;
}>;
expect(Array.isArray(extensions)).toBe(true);
expect(extensions.length).toBeGreaterThan(0);

// The host registers exactly three extensions on the app module today:
// a SignInPage, a Translation override (catalog-import), and a
// LogRowAction renderer. Overview-slot cards (OpenChoreoAboutCard,
// WorkflowsOrExternalCICard) used to live here but moved back into the
// hand-authored `entityPage` JSX when we restored the custom
// page:catalog/entity override — see customOverrides.tsx for context.
expect(extensions).toHaveLength(3);
});
});
Loading
Loading