Skip to content

Commit

Permalink
Add dynamic frontends (janus-idp#659)
Browse files Browse the repository at this point in the history
Co-authored-by: Tomas Coufal <tcoufal@redhat.com>
Co-authored-by: David Festal <dfestal@redhat.com>
  • Loading branch information
3 people committed Nov 1, 2023
1 parent c5d4177 commit da696fd
Show file tree
Hide file tree
Showing 26 changed files with 977 additions and 257 deletions.
25 changes: 25 additions & 0 deletions .changeset/twelve-peas-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
'backstage-plugin-catalog-backend-module-github-org': minor
'backstage-plugin-scaffolder-backend-module-gitlab': minor
'immobiliarelabs-backstage-plugin-gitlab-backend': minor
'backstage-plugin-catalog-backend-module-github': minor
'backstage-plugin-catalog-backend-module-gitlab': minor
'janus-idp-backstage-plugin-keycloak-backend': minor
'roadiehq-backstage-plugin-argo-cd-backend': minor
'roadiehq-scaffolder-backend-module-utils': minor
'janus-idp-backstage-plugin-aap-backend': minor
'janus-idp-backstage-plugin-ocm-backend': minor
'backstage-plugin-azure-devops-backend': minor
'backstage-plugin-kubernetes-backend': minor
'backstage-plugin-sonarqube-backend': minor
'roadiehq-scaffolder-backend-argocd': minor
'backstage-plugin-techdocs-backend': minor
'backstage-plugin-jenkins-backend': minor
'@internal/plugin-scalprum-backend': minor
'dynamic-plugins-imports': minor
'dynamic-plugins-utils': minor
'backend': minor
'app': minor
---

Enabling dynamic frontend plugins
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,9 @@ dynamic-plugins-root/*
!dynamic-plugins-root/.gitkeep
dynamic-plugins/wrappers/*/dist-dynamic/src
dynamic-plugins/imports/*/
dynamic-plugins/*/dist-dynamic/src
dynamic-plugins/wrappers/*/dist-dynamic/src
dynamic-plugins/imports/*/

#dev caches
.webpack-cache
1 change: 1 addition & 0 deletions app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -342,3 +342,4 @@ enabled:

dynamicPlugins:
rootDirectory: dynamic-plugins-root
frontend: {}
30 changes: 30 additions & 0 deletions packages/app/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,34 @@ export interface Config {
};
};
};
/** @deepVisibility frontend */
dynamicPlugins: {
/** @deepVisibility frontend */
frontend: {
[key: string]: {
dynamicRoutes: ({
[key: string]: any;
} & {
path: string;
module: string;
importName: string;
menuItem: {
icon: string;
text: string;
};
})[];
routeBindings: {
bindTarget: string;
bindMap: {
[key: string]: string;
};
}[];
mountPoints: {
mountPoint: string;
module: string;
importName?: string;
}[];
};
};
};
}
10 changes: 8 additions & 2 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"role": "frontend"
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"start": "janus-cli package start",
"build": "janus-cli package build",
"clean": "backstage-cli package clean",
"test": "backstage-cli package test --coverage",
"lint": "backstage-cli package lint"
Expand All @@ -17,6 +17,7 @@
"@backstage/app-defaults": "1.4.4",
"@backstage/catalog-model": "1.4.3",
"@backstage/cli": "0.23.1",
"@backstage/config": "^1.1.1",
"@backstage/core-app-api": "1.11.0",
"@backstage/core-components": "0.13.7",
"@backstage/core-plugin-api": "1.7.0",
Expand Down Expand Up @@ -68,7 +69,10 @@
"@roadiehq/backstage-plugin-github-pull-requests": "2.5.18",
"@roadiehq/backstage-plugin-jira": "2.4.11",
"@roadiehq/backstage-plugin-security-insights": "2.3.9",
"@scalprum/core": "^0.6.1",
"@scalprum/react-core": "^0.6.1",
"history": "5.3.0",
"lodash": "^4.0.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-router-dom": "6.17.0",
Expand All @@ -78,6 +82,8 @@
},
"devDependencies": {
"@backstage/test-utils": "1.4.4",
"@janus-idp/cli": "^1.3.1",
"@scalprum/react-test-utils": "0.0.5",
"@testing-library/dom": "8.20.1",
"@testing-library/jest-dom": "5.17.0",
"@testing-library/react": "12.1.5",
Expand Down
25 changes: 21 additions & 4 deletions packages/app/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import React from 'react';
import { renderWithEffects } from '@backstage/test-utils';
import App from './App';
import { removeScalprum } from '@scalprum/core';
import { mockPluginData } from '@scalprum/react-test-utils';
import TestRoot from './utils/test/TestRoot';
import { waitFor } from '@testing-library/dom';

const AppBase = React.lazy(() => import('./components/AppBase'));

describe('App', () => {
beforeEach(() => {
removeScalprum();
});
it('should render', async () => {
const { TestScalprumProvider } = mockPluginData({}, {});
process.env = {
NODE_ENV: 'test',
APP_CONFIG: [
Expand All @@ -21,7 +30,15 @@ describe('App', () => {
] as any,
};

const rendered = await renderWithEffects(<App />);
expect(rendered.baseElement).toBeInTheDocument();
});
const rendered = await renderWithEffects(
<TestScalprumProvider>
<TestRoot>
<React.Suspense fallback={null}>
<AppBase />
</React.Suspense>
</TestRoot>
</TestScalprumProvider>,
);
await waitFor(async () => expect(rendered.baseElement).toBeInTheDocument());
}, 100000);
});
196 changes: 5 additions & 191 deletions packages/app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,195 +1,9 @@
import { createApp } from '@backstage/app-defaults';
import { AppRouter, FlatRoutes } from '@backstage/core-app-api';
import {
AlertDisplay,
OAuthRequestDialog,
ProxiedSignInPage,
SignInPage,
} from '@backstage/core-components';
import { ApiExplorerPage, apiDocsPlugin } from '@backstage/plugin-api-docs';
import {
CatalogEntityPage,
CatalogIndexPage,
catalogPlugin,
} from '@backstage/plugin-catalog';
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
import {
CatalogImportPage,
catalogImportPlugin,
} from '@backstage/plugin-catalog-import';
import { HomepageCompositionRoot } from '@backstage/plugin-home';
import { orgPlugin } from '@backstage/plugin-org';
import { RequirePermission } from '@backstage/plugin-permission-react';
import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder';
import { SearchPage as BackstageSearchPage } from '@backstage/plugin-search';
import { TechRadarPage } from '@backstage/plugin-tech-radar';
import {
TechDocsIndexPage,
TechDocsReaderPage,
techdocsPlugin,
} from '@backstage/plugin-techdocs';
import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
import { UserSettingsPage } from '@backstage/plugin-user-settings';
import { UnifiedThemeProvider } from '@backstage/theme';
import LightIcon from '@mui/icons-material/WbSunny';
import DarkIcon from '@mui/icons-material/Brightness2';
import { OcmPage } from '@janus-idp/backstage-plugin-ocm';
import React from 'react';
import { Route } from 'react-router-dom';
import DynamicRoot from './components/DynamicRoot';
import { apis } from './apis';
import { Root } from './components/Root';
import { entityPage } from './components/catalog/EntityPage';
import { HomePage } from './components/home/HomePage';
import { LearningPaths } from './components/learningPaths/LearningPathsPage';
import { SearchPage } from './components/search/SearchPage';
import { LighthousePage } from '@backstage/plugin-lighthouse';
import {
configApiRef,
githubAuthApiRef,
useApi,
} from '@backstage/core-plugin-api';
import { customLightTheme } from './themes/lightTheme';
import { customDarkTheme } from './themes/darkTheme';
import { useUpdateTheme } from './hooks/useUpdateTheme';

const app = createApp({
apis,
bindRoutes({ bind }) {
bind(catalogPlugin.externalRoutes, {
createComponent: scaffolderPlugin.routes.root,
viewTechDoc: techdocsPlugin.routes.docRoot,
createFromTemplate: scaffolderPlugin.routes.selectedTemplate,
});
bind(apiDocsPlugin.externalRoutes, {
registerApi: catalogImportPlugin.routes.importPage,
});
bind(scaffolderPlugin.externalRoutes, {
registerComponent: catalogImportPlugin.routes.importPage,
viewTechDoc: techdocsPlugin.routes.docRoot,
});
bind(orgPlugin.externalRoutes, {
catalogIndex: catalogPlugin.routes.catalogIndex,
});
},
themes: [
{
id: 'light',
title: 'Light Theme',
variant: 'light',
icon: <LightIcon />,
Provider: ({ children }) => {
const themeColors = useUpdateTheme('light');
return (
<UnifiedThemeProvider
theme={customLightTheme(themeColors)}
children={children}
/>
);
},
},
{
id: 'dark',
title: 'Dark Theme',
variant: 'dark',
icon: <DarkIcon />,
Provider: ({ children }) => {
const themeColors = useUpdateTheme('dark');
return (
<UnifiedThemeProvider
theme={customDarkTheme(themeColors)}
children={children}
/>
);
},
},
],
components: {
SignInPage: props => {
const configApi = useApi(configApiRef);
if (configApi.getString('auth.environment') === 'development') {
return (
<SignInPage
{...props}
title="Select a sign-in method"
align="center"
providers={[
'guest',
{
id: 'github-auth-provider',
title: 'GitHub',
message: 'Sign in using GitHub',
apiRef: githubAuthApiRef,
},
]}
/>
);
}
return <ProxiedSignInPage {...props} provider="oauth2Proxy" />;
},
},
});
import React from 'react';

// `routes` and every subsequent child needs to be static JSX, so the router can traverse the tree without rendering.
// This is why we can't use a function component here.
const routes = (
<FlatRoutes>
<Route path="/" element={<HomepageCompositionRoot />}>
<HomePage />
</Route>
<Route path="/catalog" element={<CatalogIndexPage />} />
<Route
path="/catalog/:namespace/:kind/:name"
element={<CatalogEntityPage />}
>
{entityPage}
</Route>
<Route path="/docs" element={<TechDocsIndexPage />} />
<Route
path="/docs/:namespace/:kind/:name/*"
element={<TechDocsReaderPage />}
>
<TechDocsAddons>
<ReportIssue />
</TechDocsAddons>
</Route>
<Route
path="/create"
element={
<ScaffolderPage headerOptions={{ title: 'Golden Path Templates' }} />
}
/>
<Route path="/api-docs" element={<ApiExplorerPage />} />
<Route
path="/tech-radar"
element={<TechRadarPage width={1500} height={800} id="default" />}
/>
<Route
path="/catalog-import"
element={
<RequirePermission permission={catalogEntityCreatePermission}>
<CatalogImportPage />
</RequirePermission>
}
/>
<Route path="/search" element={<BackstageSearchPage />}>
<SearchPage />
</Route>
<Route path="/settings" element={<UserSettingsPage />} />
<Route path="/catalog-graph" element={<CatalogGraphPage />} />
<Route path="/ocm" element={<OcmPage />} />
<Route path="/learning-paths" element={<LearningPaths />} />
<Route path="/lighthouse" element={<LighthousePage />} />
</FlatRoutes>
const AppRoot = () => (
<DynamicRoot apis={apis} afterInit={() => import('./components/AppBase')} />
);

export default app.createRoot(
<>
<AlertDisplay />
<OAuthRequestDialog />
<AppRouter>
<Root>{routes}</Root>
</AppRouter>
</>,
);
export default AppRoot;
6 changes: 6 additions & 0 deletions packages/app/src/bootstrap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import '@backstage/cli/asset-types';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));
Loading

0 comments on commit da696fd

Please sign in to comment.