From 8462548e8f1c39e44fb06f802127241a3fb6052d Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 17 Oct 2023 14:51:04 +0200 Subject: [PATCH] feat: move legacy collect routes Signed-off-by: Camila Belo Co-authored-by: Patrik Oldsberg --- packages/core-compat-api/package.json | 14 ++- .../src/collectLegacyRoutes.test.tsx | 65 +++++++++++ .../src/collectLegacyRoutes.tsx | 103 ++++++++++++++++++ yarn.lock | 9 ++ 4 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 packages/core-compat-api/src/collectLegacyRoutes.test.tsx create mode 100644 packages/core-compat-api/src/collectLegacyRoutes.tsx diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 76c3bc6b4e..19d3104103 100644 --- a/packages/core-compat-api/package.json +++ b/packages/core-compat-api/package.json @@ -25,9 +25,21 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", + "@backstage/core-app-api": "workspace:^", + "@backstage/plugin-puppetdb": "workspace:^", + "@backstage/plugin-stackstorm": "workspace:^", + "@oriflame/backstage-plugin-score-card": "^0.7.0", "@testing-library/jest-dom": "^5.10.1" }, "files": [ "dist" - ] + ], + "peerDependencies": { + "react": "*", + "react-router-dom": "*" + }, + "dependencies": { + "@backstage/core-plugin-api": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^" + } } diff --git a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx new file mode 100644 index 0000000000..530013c283 --- /dev/null +++ b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx @@ -0,0 +1,65 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { FlatRoutes } from '@backstage/core-app-api'; +import { createPageExtension } from '@backstage/frontend-plugin-api'; +import { PuppetDbPage } from '@backstage/plugin-puppetdb'; +import { StackstormPage } from '@backstage/plugin-stackstorm'; +import { ScoreBoardPage } from '@oriflame/backstage-plugin-score-card'; +import { Route } from 'react-router-dom'; + +import { getComponentData } from '@backstage/core-plugin-api'; +import { collectLegacyRoutes } from './collectLegacyRoutes'; + +jest.mock('@backstage/frontend-plugin-api', () => ({ + ...jest.requireActual('@backstage/frontend-plugin-api'), + createPageExtension: opts => opts, +})); + +describe('collectLegacyRoutes', () => { + it('should collect legacy routes', () => { + const collected = collectLegacyRoutes( + + } /> + } /> + } /> + , + ); + + expect(collected).toEqual([ + createPageExtension({ + id: 'plugin.score-card.page', + defaultPath: 'score-board', + routeRef: getComponentData(, 'core.mountPoint'), + loader: expect.any(Function), + }), + createPageExtension({ + id: 'plugin.stackstorm.page', + defaultPath: 'stackstorm', + routeRef: getComponentData(, 'core.mountPoint'), + loader: expect.any(Function), + }), + createPageExtension({ + id: 'plugin.puppetDb.page', + defaultPath: 'puppetdb', + routeRef: getComponentData(, 'core.mountPoint'), + loader: expect.any(Function), + }), + // ?????????????? + ]); + }); +}); diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx new file mode 100644 index 0000000000..b40ae78439 --- /dev/null +++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx @@ -0,0 +1,103 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { ReactNode } from 'react'; +import { Extension, createPageExtension } from '@backstage/frontend-plugin-api'; +import { Route, Routes } from 'react-router-dom'; +import { + BackstagePlugin, + RouteRef, + getComponentData, +} from '@backstage/core-plugin-api'; + +export function collectLegacyRoutes( + flatRoutesElement: JSX.Element, +): Extension[] { + // const results = traverseElementTree({ + // root, + // discoverers: [childDiscoverer, routeElementDiscoverer], + // collectors: { + // foo: createCollector( + // () => new Set(), + // (acc, node) => { + // const plugin = getComponentData(node, 'core.plugin'); + // if (plugin) { + // acc.add(plugin); + // } + // }, + // ) + // }, + // }) + const results = new Array>(); + + React.Children.forEach( + flatRoutesElement.props.children, + (route: ReactNode) => { + if (!React.isValidElement(route)) { + return; + } + + // TODO(freben): Handle feature flag and permissions framework wrapper elements + if (route.type !== Route) { + return; + } + + const routeElement = route.props.element; + + // TODO: to support deeper extension component, e.g. hidden within , use https://github.com/backstage/backstage/blob/518a34646b79ec2028cc0ed6bc67d4366c51c4d6/packages/core-app-api/src/routing/collectors.tsx#L69 + const plugin = getComponentData( + routeElement, + 'core.plugin', + ); + if (!plugin) { + return; + } + + const routeRef = getComponentData( + routeElement, + 'core.mountPoint', + ); + + const pluginId = plugin.getId(); + const path: string = route.props.path; + + const detectedExtension = createPageExtension({ + id: `plugin.${pluginId}.page`, + defaultPath: path[0] === '/' ? path.slice(1) : path, + routeRef, + + loader: async () => + route.props.children ? ( + + + + + + ) : ( + routeElement + ), + }); + + plugin.getApis(); // Create DI API extensions from these + + // TODO: Create converted plugin instance instead. We need to move over APIs etc. + results.push(detectedExtension); + }, + ); + + // TODO: For every legacy plugin that we find, make sure any matching plugin is disabled in the new system + return results; +} diff --git a/yarn.lock b/yarn.lock index 0d17afeec6..1c101630dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3957,7 +3957,16 @@ __metadata: resolution: "@backstage/core-compat-api@workspace:packages/core-compat-api" dependencies: "@backstage/cli": "workspace:^" + "@backstage/core-app-api": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/plugin-puppetdb": "workspace:^" + "@backstage/plugin-stackstorm": "workspace:^" + "@oriflame/backstage-plugin-score-card": ^0.7.0 "@testing-library/jest-dom": ^5.10.1 + peerDependencies: + react: "*" + react-router-dom: "*" languageName: unknown linkType: soft