update NFS header migration scope

Roll back the TechDocs, catalog graph, and catalog import migrations for now while keeping the shared sub-page routing fix and the page-level header cleanup for search, notifications, and catalog entity pages.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-17 16:52:45 +01:00
parent 5f3f5d298b
commit f6a5144861
18 changed files with 343 additions and 704 deletions
@@ -24,7 +24,7 @@ import {
createExtensionBlueprint,
createExtensionInput,
} from '../wiring';
import { waitFor } from '@testing-library/react';
import { waitFor, screen } from '@testing-library/react';
describe('PageBlueprint', () => {
const mockRouteRef = createRouteRef();
@@ -279,4 +279,50 @@ describe('PageBlueprint', () => {
}
`);
});
it('should resolve sub-page tab hrefs relative to the parent page', async () => {
const myPage = PageBlueprint.make({
name: 'test-page',
params: {
path: '/test',
routeRef: mockRouteRef,
title: 'Test',
},
});
const SubPageBlueprint = createExtensionBlueprint({
kind: 'sub-page',
attachTo: { id: 'page:test-page', input: 'pages' },
output: [
coreExtensionData.routePath,
coreExtensionData.reactElement,
coreExtensionData.title.optional(),
],
factory() {
return [
coreExtensionData.routePath('config'),
coreExtensionData.title('Config'),
coreExtensionData.reactElement(<div>Config page</div>),
];
},
});
const tester = createExtensionTester(myPage).add(
SubPageBlueprint.make({ name: 'config', params: {} }),
);
renderInTestApp(tester.reactElement(), {
mountedRoutes: {
'/test/*': mockRouteRef,
},
initialRouteEntries: ['/test'],
});
await waitFor(() =>
expect(screen.getByRole('tab', { name: 'Config' })).toHaveAttribute(
'href',
'/test/config',
),
);
});
});
@@ -15,7 +15,7 @@
*/
import { JSX } from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import { Routes, Route, Navigate, useResolvedPath } from 'react-router-dom';
import { IconElement } from '../icons/types';
import { RouteRef } from '../routing';
import {
@@ -72,6 +72,7 @@ export const PageBlueprint = createExtensionBlueprint({
{ config, node, inputs },
) {
const title = config.title ?? params.title;
const routePath = config.path ?? params.path;
const icon = params.icon;
const pluginId = node.spec.plugin.pluginId;
const noHeader = params.noHeader ?? false;
@@ -79,7 +80,7 @@ export const PageBlueprint = createExtensionBlueprint({
title ?? node.spec.plugin.title ?? node.spec.plugin.pluginId;
const resolvedIcon = icon ?? node.spec.plugin.icon;
yield coreExtensionData.routePath(config.path ?? params.path);
yield coreExtensionData.routePath(routePath);
if (params.loader) {
const loader = params.loader;
const PageContent = () => {
@@ -99,24 +100,34 @@ export const PageBlueprint = createExtensionBlueprint({
};
yield coreExtensionData.reactElement(<PageContent />);
} else if (inputs.pages.length > 0) {
// Parent page with sub-pages - render header with tabs
const tabs: PageLayoutTab[] = inputs.pages.map(page => {
const path = page.get(coreExtensionData.routePath);
const tabTitle = page.get(coreExtensionData.title);
const tabIcon = page.get(coreExtensionData.icon);
return {
id: path,
label: tabTitle || path,
icon: tabIcon,
href: path,
};
});
const PageContent = () => {
const firstPagePath = inputs.pages[0]?.get(coreExtensionData.routePath);
const headerActionsApi = useApi(pluginHeaderActionsApiRef);
const headerActions = headerActionsApi.getPluginHeaderActions(pluginId);
const parentPath = useResolvedPath('.').pathname.replace(/\/$/, '');
const staticParentPath =
routePath.startsWith('/') &&
!routePath.includes('/:') &&
!routePath.includes('*')
? routePath.replace(/\/$/, '')
: undefined;
const tabs: PageLayoutTab[] = inputs.pages.map(page => {
const path = page.get(coreExtensionData.routePath);
const tabTitle = page.get(coreExtensionData.title);
const tabIcon = page.get(coreExtensionData.icon);
const tabPath = path.replace(/^\/+/, '');
const basePath = staticParentPath ?? parentPath ?? '';
const href = path.startsWith('/')
? path
: `${basePath}/${tabPath}`.replace(/\/{2,}/g, '/');
return {
id: path,
label: tabTitle || path,
icon: tabIcon,
href,
};
});
return (
<PageLayout