app-next: working graphiql page

Co-authored-by: Fredrik Adelöw <freben@gmail.com>
Co-authored-by: Johan Haals <johan.haals@gmail.com>
Co-authored-by: MT Lewis <mtlewis@users.noreply.github.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-06-14 11:42:31 +02:00
parent 0f514f99a2
commit 9ebc561ca1
3 changed files with 228 additions and 66 deletions
+4 -1
View File
@@ -1,7 +1,10 @@
app:
packages: 'all' # ✨
extensions: []
extensions:
- graphiql.page:
config:
path: /
# - core.signInPage:
# props:
# provider:
+1 -1
View File
@@ -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",
+223 -64
View File
@@ -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<string, ExtensionDataRef<any>>;
@@ -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: <route.component />,
})),
);
return element;
};
bind.component(() => (
<BrowserRouter>
<Routes />
</BrowserRouter>
));
},
});
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<string, ExtensionInstanceConfig[]>
>();
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<string, ExtensionInstance>();
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 => (
<Component />
))}
</>
);
},
};
}
/* graphiql package */
const GraphiqlPageExtension = createExtension({
output: {
component: coreExtensionData.reactComponent,
path: coreExtensionData.routePath,
},
factory({ bind, config }) {
bind.component(() => {
return <GraphiQLPage />;
});
// 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 = (
<FlatRoutes>
{/* <Route path="/" element={<Navigate to="catalog" />} />
<Route path="/catalog" element={<CatalogIndexPage />} />
<Route
path="/catalog/:namespace/:kind/:name"
element={<CatalogEntityPage />}
>
<EntityLayout>
<EntityLayout.Route path="/" title="Overview">
<Grid container spacing={3} alignItems="stretch">
<Grid item md={6} xs={12}>
<EntityAboutCard variant="gridItem" />
</Grid>
const legacyApp = createLegacyApp({ plugins: [legacyGraphiqlPlugin] });
<Grid item md={4} xs={12}>
<EntityLinksCard />
</Grid>
</Grid>
</EntityLayout.Route>
export default legacyApp.createRoot(app.createRoot());
<EntityLayout.Route path="/todos" title="TODOs">
<EntityTodoContent />
</EntityLayout.Route>
</EntityLayout>
</Route>
<Route
path="/catalog-import"
element={
<CatalogImportPage />
}
/> */}
{/* <Route
path="/tech-radar"
element={<TechRadarPage width={1500} height={800} />}
/> */}
<Route path="/graphiql" element={<GraphiQLPage />} />
</FlatRoutes>
);
// const routes = (
// <FlatRoutes>
// {/* <Route path="/" element={<Navigate to="catalog" />} />
// <Route path="/catalog" element={<CatalogIndexPage />} />
// <Route
// path="/catalog/:namespace/:kind/:name"
// element={<CatalogEntityPage />}
// >
// <EntityLayout>
// <EntityLayout.Route path="/" title="Overview">
// <Grid container spacing={3} alignItems="stretch">
// <Grid item md={6} xs={12}>
// <EntityAboutCard variant="gridItem" />
// </Grid>
export default app.createRoot(
<>
{/* <AlertDisplay transientTimeoutMs={2500} />
<OAuthRequestDialog /> */}
<AppRouter>{routes}</AppRouter>
</>,
);
// <Grid item md={4} xs={12}>
// <EntityLinksCard />
// </Grid>
// </Grid>
// </EntityLayout.Route>
// <EntityLayout.Route path="/todos" title="TODOs">
// <EntityTodoContent />
// </EntityLayout.Route>
// </EntityLayout>
// </Route>
// <Route
// path="/catalog-import"
// element={
// <CatalogImportPage />
// }
// /> */}
// {/* <Route
// path="/tech-radar"
// element={<TechRadarPage width={1500} height={800} />}
// /> */}
// <Route path="/graphiql" element={<GraphiQLPage />} />
// </FlatRoutes>
// );
// export default app.createRoot(
// <>
// {/* <AlertDisplay transientTimeoutMs={2500} />
// <OAuthRequestDialog /> */}
// <AppRouter>{routes}</AppRouter>
// </>,
// );