Skip to content

Commit

Permalink
feat(dynamic-plugins): make mountpoints and layout declarative
Browse files Browse the repository at this point in the history
Signed-off-by: Tomas Coufal <tcoufal@redhat.com>
  • Loading branch information
tumido committed Nov 1, 2023
1 parent da696fd commit a7135fe
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 31 deletions.
44 changes: 43 additions & 1 deletion app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -342,4 +342,46 @@ enabled:

dynamicPlugins:
rootDirectory: dynamic-plugins-root
frontend: {}
frontend:
janus-idp.backstage-plugin-quay:
mountPoints:
- mountPoint: entity.page.overview/cards
config:
if:
anyOf:
- isQuayAvailable
module: QuayPlugin
importName: QuayPage
janus-idp.backstage-plugin-ocm:
dynamicRoutes:
- path: /ocm
importName: OcmPage
module: OcmPlugin
menuItem:
icon: Storage
text: Clusters
mountPoints:
- mountPoint: entity.page.overview/context
importName: ClusterContextProvider
module: OcmPlugin
- mountPoint: entity.page.overview/cards
importName: ClusterAvailableResourceCard
module: OcmPlugin
config:
grid:
row: 2
column: 1
if:
anyOf:
- isKind: resource
- isType: kubernetes-cluster
- mountPoint: entity.page.overview/cards
importName: ClusterInfoCard
module: OcmPlugin
config:
if:
allOf:
- isKind: resource
- isType: kubernetes-cluster


23 changes: 20 additions & 3 deletions packages/app/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export interface Config {
text: string;
};
})[];
routeBindings: {
routeBindings?: {
bindTarget: string;
bindMap: {
[key: string]: string;
Expand All @@ -74,8 +74,25 @@ export interface Config {
mountPoints: {
mountPoint: string;
module: string;
importName?: string;
}[];
importName: string;
config: {
grid?: {
row?: number;
column?: number;
},
if?: {
allOf?: ({
[key: string]: string | string[];
} | string)[];
anyOf?: ({
[key: string]: string | string[];
} | string)[];
oneOf?: ({
[key: string]: string | string[];
} | string)[];
}
}[];
}
};
};
};
Expand Down
2 changes: 0 additions & 2 deletions packages/app/src/components/AppBase/AppBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
import { UserSettingsPage } from '@backstage/plugin-user-settings';
import { OcmPage } from '@janus-idp/backstage-plugin-ocm';
import React, { useContext } from 'react';
import { Route } from 'react-router-dom';
import { Root } from '../Root';
Expand Down Expand Up @@ -83,7 +82,6 @@ const AppBase = () => {
</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 />} />
{dynamicRoutes.map(({ Component, path, ...props }) => (
Expand Down
18 changes: 16 additions & 2 deletions packages/app/src/components/DynamicRoot/DynamicRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,20 @@ const DynamicRoot = ({
}

const providerMountPoints = mountPoints.map(
({ module, importName, mountPoint, scope }) => ({
({ module, importName, mountPoint, scope, config }) => ({
mountPoint,
Component: remotePlugins[scope][module][importName],
config: {
...config,
if: Object.fromEntries(
Object.entries(config?.if || {}).map(([k, v]) => [
k,
v.map(c =>
typeof c === 'string' ? remotePlugins[scope][module][c] : c,
),
]),
),
},
}),
);

Expand All @@ -97,7 +108,10 @@ const DynamicRoot = ({
if (!acc[entry.mountPoint]) {
acc[entry.mountPoint] = [];
}
acc[entry.mountPoint].push(entry.Component);
acc[entry.mountPoint].push({
component: entry.Component,
config: entry.config,
});
return acc;
}, {});
getScalprum().api.mountPoints = mountPointComponents;
Expand Down
17 changes: 16 additions & 1 deletion packages/app/src/components/DynamicRoot/DynamicRootContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,22 @@ export type DynamicRootContextValue = DynamicModuleEntry & {
Component: React.ComponentType<any>;
};

export type ScalprumMountPoint = React.ComponentType<{}>;
export type ScalprumMountPointConfig = {
grid?: {
row?: number;
column?: number;
},
if?: {
[key in ('allOf' | 'oneOf' | 'anyOf')]?: ({
[key: string]: string | string[];
} | Function)[];
}
}

export type ScalprumMountPoint = {
component: React.ComponentType<{}>
config?: ScalprumMountPointConfig
};

export type RemotePlugins = {
[scope: string]: {
Expand Down
89 changes: 68 additions & 21 deletions packages/app/src/components/catalog/EntityPage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,74 @@
import { EntitySwitch, isKind } from '@backstage/plugin-catalog';
import React from 'react';
import getMountPointData from '../../../utils/dynamicUI/getMountPointData';
import { EntityLayout, EntitySwitch, isKind } from '@backstage/plugin-catalog';
import { ScalprumMountPointConfig } from '../../DynamicRoot/DynamicRootContext';
import { isType } from '../utils';
import { Entity } from '@backstage/catalog-model';

import {
componentPage,
defaultEntityPage,
apiPage,
groupPage,
userPage,
systemPage,
domainPage,
resourcePage,
} from './Pages';

const createConditionsArray = (conditions: ScalprumMountPointConfig['if']['allOf']) => {
return conditions.map(c => {
if (typeof c === 'function') {
return c
}
if (c.isKind) {
return isKind(c.isKind)
}
if (c.isType) {
return isType(c.isType)
}
return () => true
})
}

const entitySwitchCaseIf = (conditional: ScalprumMountPointConfig['if']) => (e: Entity) => {
if (conditional?.allOf) {
return createConditionsArray(conditional.allOf).every(f => f(e))
}
if (conditional?.anyOf) {
return createConditionsArray(conditional.anyOf).some(f => f(e))
}
if (conditional?.oneOf) {
return createConditionsArray(conditional.oneOf).filter(f => f(e)).length === 1
}
return true
}

export const entityPage = (
<EntitySwitch>
<EntitySwitch.Case if={isKind('component')} children={componentPage} />
<EntitySwitch.Case if={isKind('api')} children={apiPage} />
<EntitySwitch.Case if={isKind('group')} children={groupPage} />
<EntitySwitch.Case if={isKind('user')} children={userPage} />
<EntitySwitch.Case if={isKind('system')} children={systemPage} />
<EntitySwitch.Case if={isKind('domain')} children={domainPage} />
<EntitySwitch.Case if={isKind('resource')} children={resourcePage} />
<EntityLayout>
<EntityLayout.Route path="/" title="Overview">
<>
{getMountPointData<React.ComponentType>('entity.page.overview/context').reduce(
(acc, { component: Component }) => (
<Component>
{acc}
</Component>
),
<>
{getMountPointData<React.ComponentType>('entity.page.overview/cards').map(
({ component: Component, config }, index) => (
<EntitySwitch key={index}>
<EntitySwitch.Case if={entitySwitchCaseIf(config.if)}>
<Component />
</EntitySwitch.Case>
</EntitySwitch>
)
)}
</>)
}
</>
</EntityLayout.Route>
</EntityLayout>
// <EntitySwitch>
// <EntitySwitch.Case if={isKind('component')} children={componentPage} />
// <EntitySwitch.Case if={isKind('api')} children={apiPage} />
// <EntitySwitch.Case if={isKind('group')} children={groupPage} />
// <EntitySwitch.Case if={isKind('user')} children={userPage} />
// <EntitySwitch.Case if={isKind('system')} children={systemPage} />
// <EntitySwitch.Case if={isKind('domain')} children={domainPage} />
// <EntitySwitch.Case if={isKind('resource')} children={resourcePage} />

// <EntitySwitch.Case>{defaultEntityPage}</EntitySwitch.Case>
// </EntitySwitch>

<EntitySwitch.Case>{defaultEntityPage}</EntitySwitch.Case>
</EntitySwitch>
);
2 changes: 2 additions & 0 deletions packages/app/src/utils/dynamicUI/extractDynamicConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { defaultConfigLoader } from '@backstage/core-app-api';
import {
DynamicModuleEntry,
RouteBinding,
ScalprumMountPointConfig,
} from '../../components/DynamicRoot/DynamicRootContext';

type AppConfig = {
Expand Down Expand Up @@ -48,6 +49,7 @@ async function extractDynamicConfig() {
module: string;
importName: string;
mountPoint: string;
config?: ScalprumMountPointConfig;
}[];
}>(
(acc, { data }) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/utils/dynamicUI/getMountPointData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getScalprum } from '@scalprum/core';
import { ScalprumMountPointConfig } from '../../components/DynamicRoot/DynamicRootContext';

function getMountPointData<T = any>(mountPoint: string): T[] {
function getMountPointData<T = any>(mountPoint: string): {config: ScalprumMountPointConfig; component: T}[] {
return getScalprum().api.mountPoints?.[mountPoint] ?? [];
}

Expand Down

0 comments on commit a7135fe

Please sign in to comment.