From 9ebc561ca15b8613f63f08b126bd9b467e31721e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 14 Jun 2023 11:42:31 +0200 Subject: [PATCH] app-next: working graphiql page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: Johan Haals Co-authored-by: MT Lewis Signed-off-by: Patrik Oldsberg --- packages/app-next/app-config.yaml | 5 +- packages/app-next/package.json | 2 +- packages/app-next/src/App.tsx | 287 +++++++++++++++++++++++------- 3 files changed, 228 insertions(+), 66 deletions(-) diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index 6042c5ce2a..a15c053797 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -1,7 +1,10 @@ app: packages: 'all' # ✨ - extensions: [] + extensions: + - graphiql.page: + config: + path: / # - core.signInPage: # props: # provider: diff --git a/packages/app-next/package.json b/packages/app-next/package.json index 35a8e02cbc..776e611c60 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -112,7 +112,7 @@ "start-server-and-test": "^1.10.11" }, "scripts": { - "start": "backstage-cli package start --config app-config.yaml --config packages/app-next/app-config.yaml", + "start": "backstage-cli package start --config ../../app-config.yaml --config app-config.yaml", "build": "backstage-cli package build", "clean": "backstage-cli package clean", "test": "backstage-cli package test", diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index a93a934301..5d2a202667 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -13,28 +13,48 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; -import { catalogPlugin } from '@internal/plugin-catalog-customized'; - -import { catalogImportPlugin } from '@backstage/plugin-catalog-import'; -import { GraphiQLPage } from '@backstage/plugin-graphiql'; -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { TechRadarPage } from '@backstage/plugin-tech-radar'; +import { + Router as GraphiQLPage, + graphiqlPlugin as legacyGraphiqlPlugin, +} from '@backstage/plugin-graphiql'; +import { createApp as createLegacyApp } from '@backstage/app-defaults'; import React, { ComponentType } from 'react'; -import { Route } from 'react-router-dom'; +import { BrowserRouter, useRoutes } from 'react-router-dom'; import mapValues from 'lodash/mapValues'; /* core */ -const discoverPackages = async () => { - // stub for now, deferring package discovery til later - return ['@backstage/plugin-graphiql']; -}; +// const discoverPackages = async () => { +// // stub for now, deferring package discovery til later +// return ['@backstage/plugin-graphiql']; +// }; -function createApp() {} +interface ExtensionInstanceConfig { + id: string; + at: string; + extension: Extension; + config: unknown; +} -function createPlugin() {} +interface BackstagePluginOptions { + id: string; + defaultExtensionInstances?: ExtensionInstanceConfig[]; +} + +interface BackstagePlugin { + $$type: 'backstage-plugin'; + id: string; + defaultExtensionInstances: ExtensionInstanceConfig[]; +} + +function createPlugin(options: BackstagePluginOptions): BackstagePlugin { + return { + ...options, + $$type: 'backstage-plugin', + defaultExtensionInstances: options.defaultExtensionInstances ?? [], + }; +} type AnyExtensionDataMap = Record>; @@ -130,22 +150,157 @@ function createExtensionInstance(options: { return { id: options.id, data: extensionData, $$type: 'extension-instance' }; } -/* graphiql package */ +/* core extensions */ -const GraphiqlRoute = createExtension({ +const RouteExtension = createExtension({ inputs: { - extensionData: { - component: coreExtensionData.reactComponent, + routes: { + extensionData: { + path: coreExtensionData.routePath, + component: coreExtensionData.reactComponent, + }, }, }, - factory({ bind, points }) { - // ... + output: { + component: coreExtensionData.reactComponent, }, + factory({ bind, inputs }) { + const Routes = () => { + const element = useRoutes( + inputs.routes.map(route => ({ + path: route.path, + element: , + })), + ); + + return element; + }; + bind.component(() => ( + + + + )); + }, +}); + +function createApp(options: { plugins: BackstagePlugin[] }): { + createRoot(): JSX.Element; +} { + // pull in default extension instance from discovered packages + // apply config to adjust default extension instances and add more + const extensionInstanceConfigs = [ + ...options.plugins.flatMap(plugin => plugin.defaultExtensionInstances), + { + id: 'core.router', + at: 'root/default', + extension: RouteExtension, + config: undefined, + }, + ]; + + const attachmentMap = new Map< + string, + Map + >(); + for (const config of extensionInstanceConfigs) { + const [extensionId, pointId = 'default'] = config.at.split('/'); + + let pointMap = attachmentMap.get(extensionId); + if (!pointMap) { + pointMap = new Map(); + attachmentMap.set(extensionId, pointMap); + } + + let instances = pointMap.get(pointId); + if (!instances) { + instances = []; + pointMap.set(pointId, instances); + } + + instances.push(config); + } + + const instances = new Map(); + + function createInstance(config: ExtensionInstanceConfig): ExtensionInstance { + const existingInstance = instances.get(config.id); + if (existingInstance) { + return existingInstance; + } + + const attachments = Object.fromEntries( + Array.from(attachmentMap.get(config.id)?.entries() ?? []).map( + ([inputName, attachmentConfigs]) => [ + inputName, + attachmentConfigs.map(createInstance), + ], + ), + ); + + return createExtensionInstance({ + id: config.id, + config: config.config, + extension: config.extension, + attachments, + }); + } + + const rootConfigs = attachmentMap.get('root')?.get('default') ?? []; + const rootInstances = rootConfigs.map(instanceConfig => + createInstance(instanceConfig), + ); + + return { + createRoot() { + const rootComponents = rootInstances.map( + e => + e.data.get( + coreExtensionData.reactComponent.id, + ) as typeof coreExtensionData.reactComponent.T, + ); + return ( + <> + {rootComponents.map(Component => ( + + ))} + + ); + }, + }; +} + +/* graphiql package */ + +const GraphiqlPageExtension = createExtension({ + output: { + component: coreExtensionData.reactComponent, + path: coreExtensionData.routePath, + }, + factory({ bind, config }) { + bind.component(() => { + return ; + }); + // TODO stop it. I'm serious + bind.path((config as { path: string }).path); + }, +}); + +const graphiqlPlugin = createPlugin({ + id: 'graphiql', + defaultExtensionInstances: [ + { + id: 'graphiql.page', + at: 'core.router/routes', + extension: GraphiqlPageExtension, + config: { path: '/graphiql' }, + }, + ], }); /* app.tsx */ const app = createApp({ + plugins: [graphiqlPlugin], // bindRoutes({ bind }) { // bind(catalogPlugin.externalRoutes, { // createComponent: scaffolderPlugin.routes.root, @@ -156,50 +311,54 @@ const app = createApp({ // }, }); -const routes = ( - - {/* } /> - } /> - } - > - - - - - - +const legacyApp = createLegacyApp({ plugins: [legacyGraphiqlPlugin] }); - - - - - +export default legacyApp.createRoot(app.createRoot()); - - - - - - - } - /> */} - {/* } - /> */} - } /> - -); +// const routes = ( +// +// {/* } /> +// } /> +// } +// > +// +// +// +// +// +// -export default app.createRoot( - <> - {/* - */} - {routes} - , -); +// +// +// +// +// + +// +// +// +// +// +// +// } +// /> */} +// {/* } +// /> */} +// } /> +// +// ); + +// export default app.createRoot( +// <> +// {/* +// */} +// {routes} +// , +// );