diff --git a/.changeset/bright-crabs-crash.md b/.changeset/bright-crabs-crash.md
new file mode 100644
index 0000000000..f5518505b5
--- /dev/null
+++ b/.changeset/bright-crabs-crash.md
@@ -0,0 +1,39 @@
+---
+'app': major
+---
+
+Use dynamic frontend plugins across the app
+
+This change makes `dynamicPlugins.frontend.mountPoints` generic and declarative:
+
+Mountpoint now support following names/types:
+
+- Allow passing \*/context mountpoints for React context
+- Allow passing \*/cards for Card components (with layout)
+
+- `entity.page.overview`
+- `entity.page.topology`
+- `entity.page.issues`
+- `entity.page.pull-requests`
+- `entity.page.ci`
+- `entity.page.cd`
+- `entity.page.kubernetes`
+- `entity.page.tekton`
+- `entity.page.image-registry`
+- `entity.page.monitoring`
+- `entity.page.lighthouse`
+- `entity.page.api`
+- `entity.page.dependencies`
+- `entity.page.docs`
+- `entity.page.definition`
+- `entity.page.diagram`
+
+Mountpoints support following configuration:
+
+- `layout` for layout features that propagates to allowing users to use CSS properties gridColumnStart including responsiveness queries etc. (mui.com/system/getting-started/the-sx-prop)
+- `if` for EntitySwitch.Case if=... - allows allOf|anyOf|oneOf conditionals with isKind|isType|hasAnnotation builtin methods or code imports via Scalprum (direct string reference)
+- `props` to pass additional props to the mounted component
+
+Current limitations of the dynamic frontend plugins:
+
+Allows you to mount to existing mountPoints only. You're unable to create additional tabs for example. (will be addressed in a follow up PR)
diff --git a/app-config.yaml b/app-config.yaml
index cc70dcfd5b..b769aec930 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -342,4 +342,334 @@ enabled:
dynamicPlugins:
rootDirectory: dynamic-plugins-root
- frontend: {}
+ frontend:
+ backstage.plugin-azure-devops:
+ mountPoints:
+ - mountPoint: entity.page.ci/cards
+ module: AzureDevopsPlugin
+ importName: EntityAzurePipelinesContent
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ allOf:
+ - isAzureDevOpsAvailable
+ - mountPoint: entity.page.pull-requests/cards
+ module: AzureDevopsPlugin
+ importName: EntityAzurePullRequestsContent
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ allOf:
+ - isAzureDevOpsAvailable
+ backstage.plugin-dynatrace:
+ mountPoints:
+ - mountPoint: entity.page.monitoring/cards
+ module: DynatracePlugin
+ importName: DynatraceTab
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ allOf:
+ - isDynatraceAvailable
+ backstage.plugin-github-actions:
+ mountPoints:
+ - mountPoint: entity.page.ci/cards
+ module: GithubActionsPlugin
+ importName: EntityGithubActionsContent
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ allOf:
+ - isGithubActionsAvailable
+ backstage.plugin-github-issues:
+ mountPoints:
+ - mountPoint: entity.page.issues/cards
+ module: GithubIssuesPlugin
+ importName: GithubIssuesCard
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ allOf:
+ - hasAnnotation: github.com/project-slug
+ backstage.plugin-jenkins:
+ mountPoints:
+ - mountPoint: entity.page.ci/cards
+ module: JenkinsPlugin
+ importName: EntityJenkinsContent
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ allOf:
+ - isJenkinsAvailable
+ backstage.plugin-kubernetes:
+ mountPoints:
+ - mountPoint: entity.page.kubernetes/cards
+ module: KubernetesPlugin
+ importName: EntityKubernetesContent
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ anyOf:
+ - hasAnnotation: backstage.io/kubernetes-id
+ - hasAnnotation: backstage.io/kubernetes-namespace
+ backstage.plugin-lighthouse:
+ dynamicRoutes:
+ - path: /lighthouse
+ module: LighthousePlugin
+ importName: LighthousePage
+ menuItem:
+ icon: Assessment
+ text: Lighthouse
+ mountPoints:
+ - mountPoint: entity.page.overview/cards
+ module: LighthousePlugin
+ importName: EntityLastLighthouseAuditCard
+ config:
+ layout:
+ gridColumnEnd: "span 6"
+ if:
+ allOf:
+ - isLighthouseAvailable
+ backstage.plugin-pagerduty:
+ mountPoints:
+ - mountPoint: entity.page.overview/cards
+ module: PagerdutyPlugin
+ importName: EntityPagerDutyCard
+ config:
+ layout:
+ gridColumnEnd: "span 6"
+ if:
+ allOf:
+ - isPluginApplicableToEntity
+ backstage.plugin-sonarqube:
+ mountPoints:
+ - mountPoint: entity.page.overview/cards
+ module: SonarQubePlugin
+ importName: EntitySonarQubeCard
+ config:
+ layout:
+ gridColumnStart: "span 4"
+ gridRowEnd: "span 2"
+ if:
+ allOf:
+ - isSonarQubeAvailable
+ immobiliarelabs.backstage-plugin-gitlab:
+ mountPoints:
+ - mountPoint: entity.page.overview/cards
+ module: GitlabPlugin
+ importName: EntityGitlabMergeRequestStatsCard
+ config:
+ layout:
+ gridRowEnd: "span 2"
+ gridColumnStart: "span 4"
+ if:
+ allOf:
+ - isGitlabAvailable
+ - mountPoint: entity.page.ci/cards
+ module: GitlabPlugin
+ importName: EntityGitlabPipelinesTable
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ allOf:
+ - isGitlabAvailable
+ - mountPoint: entity.page.issues/cards
+ module: GitlabPlugin
+ importName: EntityGitlabIssuesTable
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ allOf:
+ - isGitlabAvailable
+ - mountPoint: entity.page.pull-requests/cards
+ module: GitlabPlugin
+ importName: EntityGitlabMergeRequestsTable
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ allOf:
+ - isGitlabAvailable
+ janus-idp.backstage-plugin-jfrog-artifactory:
+ mountPoints:
+ - mountPoint: entity.page.image-registry/cards
+ module: JfrogArtifactoryPlugin
+ importName: JfrogArtifactoryPage
+ config:
+ layout:
+ gridColumn: 1 / -1
+ if:
+ anyOf:
+ - isJfrogArtifactoryAvailable
+ janus-idp.backstage-plugin-nexus-repository-manager:
+ mountPoints:
+ - mountPoint: entity.page.image-registry/cards
+ module: NexusRepositoryManagerPlugin
+ importName: NexusRepositoryManagerPage
+ config:
+ layout:
+ gridColumn: 1 / -1
+ if:
+ anyOf:
+ - isNexusRepositoryManagerAvailable
+ janus-idp.backstage-plugin-ocm:
+ dynamicRoutes:
+ - path: /ocm
+ module: OcmPlugin
+ importName: OcmPage
+ menuItem:
+ icon: Storage
+ text: Clusters
+ mountPoints:
+ - mountPoint: entity.page.overview/context
+ module: OcmPlugin
+ importName: ClusterContextProvider
+ - mountPoint: entity.page.overview/cards
+ module: OcmPlugin
+ importName: ClusterAvailableResourceCard
+ config:
+ layout:
+ gridRowStart: 1
+ gridColumnStart: "span 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
+ janus-idp.backstage-plugin-quay:
+ mountPoints:
+ - mountPoint: entity.page.image-registry/cards
+ module: QuayPlugin
+ importName: QuayPage
+ config:
+ layout:
+ gridColumn: 1 / -1
+ if:
+ anyOf:
+ - isQuayAvailable
+ janus-idp.backstage-plugin-tekton:
+ mountPoints:
+ - mountPoint: entity.page.ci/cards
+ module: TektonPlugin
+ importName: TektonCI
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ allOf:
+ - isTektonCIAvailable
+ janus-idp.backstage-plugin-topology:
+ mountPoints:
+ - mountPoint: entity.page.topology/cards
+ module: TopologyPlugin
+ importName: TopologyPage
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ anyOf:
+ - hasAnnotation: backstage.io/kubernetes-id
+ - hasAnnotation: backstage.io/kubernetes-namespace
+ roadiehq.backstage-plugin-argo-cd:
+ mountPoints:
+ - mountPoint: entity.page.overview/cards
+ module: ArgocdPlugin
+ importName: EntityArgoCDOverviewCard
+ config:
+ layout:
+ gridColumn: "1 / span 8"
+ if:
+ allOf:
+ - isArgocdAvailable
+ - mountPoint: entity.page.cd/cards
+ module: ArgocdPlugin
+ importName: EntityArgoCDHistoryCard
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ allOf:
+ - isArgocdAvailable
+ roadiehq.backstage-plugin-datadog:
+ mountPoints:
+ - mountPoint: entity.page.monitoring/cards
+ module: DatadogPlugin
+ importName: EntityDatadogContent
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ allOf:
+ - isDatadogAvailable
+ roadiehq.backstage-plugin-github-insights:
+ mountPoints:
+ - mountPoint: entity.page.overview/cards
+ module: GithubInsightsPlugin
+ importName: EntityGithubInsightsComplianceCard
+ config:
+ layout:
+ gridRowEnd: "span 2"
+ gridColumnStart: "span 4"
+ if:
+ allOf:
+ - isGithubInsightsAvailable
+ roadiehq.backstage-plugin-github-pull-requests:
+ mountPoints:
+ - mountPoint: entity.page.overview/cards
+ module: GithubPullRequestsPlugin
+ importName: EntityGithubPullRequestsOverviewCard
+ config:
+ layout:
+ gridRow: "1 / span 2"
+ gridColumn: "5 / span 4"
+ if:
+ allOf:
+ - isGithubPullRequestsAvailable
+ - mountPoint: entity.page.pull-requests/cards
+ module: GithubPullRequestsPlugin
+ importName: EntityGithubPullRequestsContent
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ allOf:
+ - isGithubPullRequestsAvailable
+ roadiehq.backstage-plugin-jira:
+ mountPoints:
+ - mountPoint: entity.page.issues/cards
+ module: JiraPlugin
+ importName: EntityJiraOverviewCard
+ config:
+ layout:
+ gridColumn: "1 / -1"
+ if:
+ allOf:
+ - isJiraAvailable
+ roadiehq.backstage-plugin-security-insights:
+ mountPoints:
+ - mountPoint: entity.page.overview/cards
+ module: SecurityInsightsPlugin
+ importName: EntityDependabotAlertsCard
+ config:
+ layout:
+ gridRowEnd: "span 2"
+ gridColumnStart: "span 4"
+ if:
+ allOf:
+ - isSecurityInsightsAvailable
diff --git a/packages/app/config.d.ts b/packages/app/config.d.ts
index e8732874c3..362344b162 100644
--- a/packages/app/config.d.ts
+++ b/packages/app/config.d.ts
@@ -65,7 +65,7 @@ export interface Config {
text: string;
};
})[];
- routeBindings: {
+ routeBindings?: {
bindTarget: string;
bindMap: {
[key: string]: string;
@@ -74,8 +74,36 @@ export interface Config {
mountPoints: {
mountPoint: string;
module: string;
- importName?: string;
- }[];
+ importName: string;
+ config: {
+ layout?: {
+ [key: string]: string;
+ };
+ props?: {
+ [key: string]: string;
+ };
+ if?: {
+ allOf?: (
+ | {
+ [key: string]: string | string[];
+ }
+ | string
+ )[];
+ anyOf?: (
+ | {
+ [key: string]: string | string[];
+ }
+ | string
+ )[];
+ oneOf?: (
+ | {
+ [key: string]: string | string[];
+ }
+ | string
+ )[];
+ };
+ }[];
+ };
};
};
};
diff --git a/packages/app/src/components/AppBase/AppBase.tsx b/packages/app/src/components/AppBase/AppBase.tsx
index 1a3f969cb7..738d33b140 100644
--- a/packages/app/src/components/AppBase/AppBase.tsx
+++ b/packages/app/src/components/AppBase/AppBase.tsx
@@ -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';
@@ -25,7 +24,6 @@ import { entityPage } from '../catalog/EntityPage';
import { HomePage } from '../home/HomePage';
import { LearningPaths } from '../learningPaths/LearningPathsPage';
import { SearchPage } from '../search/SearchPage';
-import { LighthousePage } from '@backstage/plugin-lighthouse';
import DynamicRootContext from '../DynamicRoot/DynamicRootContext';
const AppBase = () => {
@@ -83,9 +81,7 @@ const AppBase = () => {
} />
} />
- } />
} />
- } />
{dynamicRoutes.map(({ Component, path, ...props }) => (
{
+ if (typeof condition === 'function') {
+ return (entity: Entity) => Boolean(condition(entity));
+ }
+ if (condition.isKind) {
+ return isKind(condition.isKind);
+ }
+ if (condition.isType) {
+ return isType(condition.isType);
+ }
+ if (condition.hasAnnotation) {
+ return hasAnnotation(condition.hasAnnotation as string);
+ }
+ return () => false;
+};
+
+const configIfToCallable =
+ (conditional: ScalprumMountPointConfigRawIf) => (e: Entity) => {
+ if (conditional?.allOf) {
+ return conditional.allOf.map(conditionsArrayMapper).every(f => f(e));
+ }
+ if (conditional?.anyOf) {
+ return conditional.anyOf.map(conditionsArrayMapper).some(f => f(e));
+ }
+ if (conditional?.oneOf) {
+ return (
+ conditional.oneOf.map(conditionsArrayMapper).filter(f => f(e))
+ .length === 1
+ );
+ }
+ return true;
+ };
const DynamicRoot = ({
afterInit,
@@ -85,9 +128,22 @@ const DynamicRoot = ({
}
const providerMountPoints = mountPoints.map(
- ({ module, importName, mountPoint, scope }) => ({
+ ({ module, importName, mountPoint, scope, config }) => ({
mountPoint,
Component: remotePlugins[scope][module][importName],
+ config: {
+ ...config,
+ if: configIfToCallable(
+ Object.fromEntries(
+ Object.entries(config?.if || {}).map(([k, v]) => [
+ k,
+ v.map(c =>
+ typeof c === 'string' ? remotePlugins[scope][module][c] : c,
+ ),
+ ]),
+ ),
+ ),
+ },
}),
);
@@ -97,7 +153,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;
diff --git a/packages/app/src/components/DynamicRoot/DynamicRootContext.tsx b/packages/app/src/components/DynamicRoot/DynamicRootContext.tsx
index ec54ffe4ec..4bd4f33255 100644
--- a/packages/app/src/components/DynamicRoot/DynamicRootContext.tsx
+++ b/packages/app/src/components/DynamicRoot/DynamicRootContext.tsx
@@ -1,6 +1,7 @@
import React, { createContext } from 'react';
import { ScalprumComponentProps } from '@scalprum/react-core';
+import { Entity } from '@backstage/catalog-model';
export type RouteBinding = {
bindTarget: string;
@@ -24,7 +25,32 @@ export type DynamicRootContextValue = DynamicModuleEntry & {
Component: React.ComponentType;
};
-export type ScalprumMountPoint = React.ComponentType<{}>;
+type ScalprumMountPointConfigBase = {
+ layout?: Record;
+ props?: Record;
+};
+
+export type ScalprumMountPointConfig = ScalprumMountPointConfigBase & {
+ if: (e: Entity) => boolean | Promise;
+};
+
+export type ScalprumMountPointConfigRawIf = {
+ [key in 'allOf' | 'oneOf' | 'anyOf']?: (
+ | {
+ [key: string]: string | string[];
+ }
+ | Function
+ )[];
+};
+
+export type ScalprumMountPointConfigRaw = ScalprumMountPointConfigBase & {
+ if?: ScalprumMountPointConfigRawIf;
+};
+
+export type ScalprumMountPoint = {
+ component: React.ComponentType<{}>;
+ config?: ScalprumMountPointConfig;
+};
export type RemotePlugins = {
[scope: string]: {
diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx
index fd60fe5d2a..1348df04f6 100644
--- a/packages/app/src/components/Root/Root.tsx
+++ b/packages/app/src/components/Root/Root.tsx
@@ -21,7 +21,6 @@ import MenuIcon from '@mui/icons-material/Menu';
import MapIcon from '@mui/icons-material/MyLocation';
import SchoolIcon from '@mui/icons-material/School';
import SearchIcon from '@mui/icons-material/Search';
-import AssessmentIcon from '@mui/icons-material/Assessment';
import React, { PropsWithChildren, useContext } from 'react';
import { SidebarLogo } from './SidebarLogo';
import DynamicRootContext from '../DynamicRoot/DynamicRootContext';
@@ -61,11 +60,6 @@ export const Root = ({ children }: PropsWithChildren<{}>) => {
to="tech-radar"
text="Tech Radar"
/>
-
{dynamicRoutes.map(({ menuItem, path }) => {
if (menuItem) {
return (
diff --git a/packages/app/src/components/catalog/EntityPage/Content/CD.tsx b/packages/app/src/components/catalog/EntityPage/Content/CD.tsx
deleted file mode 100644
index c14061d2ae..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Content/CD.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import Grid from '@mui/material/Grid';
-import { EntityArgoCDHistoryCard } from '@roadiehq/backstage-plugin-argo-cd';
-import React from 'react';
-
-export const cdContent = (
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Content/CI.tsx b/packages/app/src/components/catalog/EntityPage/Content/CI.tsx
deleted file mode 100644
index 7f3d407b9a..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Content/CI.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { type Entity } from '@backstage/catalog-model';
-import {
- EntityAzurePipelinesContent,
- isAzureDevOpsAvailable,
-} from '@backstage/plugin-azure-devops';
-import { EntitySwitch } from '@backstage/plugin-catalog';
-import {
- EntityGithubActionsContent,
- isGithubActionsAvailable,
-} from '@backstage/plugin-github-actions';
-import {
- EntityGitlabPipelinesTable,
- isGitlabAvailable,
-} from '@immobiliarelabs/backstage-plugin-gitlab';
-import {
- TektonCI,
- isTektonCIAvailable,
-} from '@janus-idp/backstage-plugin-tekton';
-import Grid from '@mui/material/Grid';
-import React from 'react';
-
-const ifCIs: ((e: Entity) => boolean)[] = [
- isGithubActionsAvailable,
- isGitlabAvailable,
- isTektonCIAvailable,
- isAzureDevOpsAvailable,
-];
-
-export const isCIsAvailable = (e: Entity) => ifCIs.some(f => f(e));
-
-export const ciContent = (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Content/Dependencies.tsx b/packages/app/src/components/catalog/EntityPage/Content/Dependencies.tsx
deleted file mode 100644
index 7ddf8b0273..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Content/Dependencies.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import {
- EntityConsumedApisCard,
- EntityProvidedApisCard,
-} from '@backstage/plugin-api-docs';
-import {
- EntityDependsOnComponentsCard,
- EntityDependsOnResourcesCard,
- EntityHasSubcomponentsCard,
-} from '@backstage/plugin-catalog';
-import {
- Direction,
- EntityCatalogGraphCard,
-} from '@backstage/plugin-catalog-graph';
-import Grid from '@mui/material/Grid';
-import React from 'react';
-
-export const dependenciesContent = (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Content/Docs.tsx b/packages/app/src/components/catalog/EntityPage/Content/Docs.tsx
deleted file mode 100644
index 2f0b62a639..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Content/Docs.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { EntityTechdocsContent } from '@backstage/plugin-techdocs';
-import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
-import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
-import Grid from '@mui/material/Grid';
-import React from 'react';
-
-export const techdocsContent = (
-
-
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Content/EntityWarning.tsx b/packages/app/src/components/catalog/EntityPage/Content/EntityWarning.tsx
deleted file mode 100644
index ef72595b99..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Content/EntityWarning.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import {
- EntityOrphanWarning,
- EntityProcessingErrorsPanel,
- EntityRelationWarning,
- EntitySwitch,
- hasCatalogProcessingErrors,
- hasRelationWarnings,
- isOrphan,
-} from '@backstage/plugin-catalog';
-import Grid from '@mui/material/Grid';
-import React from 'react';
-
-export const entityWarningContent = (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Content/ImageRegistry.tsx b/packages/app/src/components/catalog/EntityPage/Content/ImageRegistry.tsx
deleted file mode 100644
index e136c9e57c..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Content/ImageRegistry.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { type Entity } from '@backstage/catalog-model';
-import { EntitySwitch } from '@backstage/plugin-catalog';
-import {
- JfrogArtifactoryPage,
- isJfrogArtifactoryAvailable,
-} from '@janus-idp/backstage-plugin-jfrog-artifactory';
-import { QuayPage, isQuayAvailable } from '@janus-idp/backstage-plugin-quay';
-import {
- isNexusRepositoryManagerAvailable,
- NexusRepositoryManagerPage,
-} from '@janus-idp/backstage-plugin-nexus-repository-manager';
-import Grid from '@mui/material/Grid';
-import React from 'react';
-
-const ifImageRegistries: ((e: Entity) => boolean)[] = [
- isQuayAvailable,
- isJfrogArtifactoryAvailable,
-];
-
-export const isImageRegistriesAvailable = (e: Entity) =>
- ifImageRegistries.some(f => f(e));
-
-export const imageRegistry = (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Content/Issues.tsx b/packages/app/src/components/catalog/EntityPage/Content/Issues.tsx
deleted file mode 100644
index 99a2ec33b3..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Content/Issues.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { type Entity } from '@backstage/catalog-model';
-import { EntitySwitch } from '@backstage/plugin-catalog';
-import { GithubIssuesCard } from '@backstage/plugin-github-issues';
-import {
- EntityGitlabIssuesTable,
- isGitlabAvailable,
-} from '@immobiliarelabs/backstage-plugin-gitlab';
-import Grid from '@mui/material/Grid';
-import { isGithubPullRequestsAvailable } from '@roadiehq/backstage-plugin-github-pull-requests';
-import {
- EntityJiraOverviewCard,
- isJiraAvailable,
-} from '@roadiehq/backstage-plugin-jira';
-import React from 'react';
-
-const ifIssues: ((e: Entity) => boolean)[] = [
- isGithubPullRequestsAvailable,
- isGitlabAvailable,
- isJiraAvailable,
-];
-
-export const isIssuesAvailable = (e: Entity) => ifIssues.some(f => f(e));
-
-export const issuesContent = (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* TODO: update GithubIssuesCard if entity check once its available */}
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Content/Monitoring.tsx b/packages/app/src/components/catalog/EntityPage/Content/Monitoring.tsx
deleted file mode 100644
index 73a5603be8..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Content/Monitoring.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { type Entity } from '@backstage/catalog-model';
-import { EntitySwitch } from '@backstage/plugin-catalog';
-import Grid from '@mui/material/Grid';
-import {
- DynatraceTab,
- isDynatraceAvailable,
-} from '@backstage/plugin-dynatrace';
-import {
- EntityDatadogContent,
- isDatadogAvailable,
-} from '@roadiehq/backstage-plugin-datadog';
-import React from 'react';
-
-const ifMonitoring: ((e: Entity) => boolean)[] = [
- isDatadogAvailable,
- isDynatraceAvailable,
-];
-
-export const isMonitoringAvailable = (e: Entity) =>
- ifMonitoring.some(f => f(e));
-
-export const monitoringContent = (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Content/Overview.tsx b/packages/app/src/components/catalog/EntityPage/Content/Overview.tsx
deleted file mode 100644
index 12db9040c4..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Content/Overview.tsx
+++ /dev/null
@@ -1,142 +0,0 @@
-import { EmptyState } from '@backstage/core-components';
-import {
- EntityAboutCard,
- EntityLinksCard,
- EntitySwitch,
-} from '@backstage/plugin-catalog';
-import Grid from '@mui/material/Grid';
-import React from 'react';
-
-import {
- EntityLatestJenkinsRunCard,
- isJenkinsAvailable,
-} from '@backstage/plugin-jenkins';
-import { EntitySonarQubeCard } from '@backstage/plugin-sonarqube';
-import { isSonarQubeAvailable } from '@backstage/plugin-sonarqube-react';
-import {
- EntityGitlabMergeRequestStatsCard,
- isGitlabAvailable,
-} from '@immobiliarelabs/backstage-plugin-gitlab';
-import { EntityArgoCDOverviewCard } from '@roadiehq/backstage-plugin-argo-cd';
-import {
- EntityGithubInsightsComplianceCard,
- isGithubInsightsAvailable,
-} from '@roadiehq/backstage-plugin-github-insights';
-import {
- EntityGithubPullRequestsOverviewCard,
- isGithubPullRequestsAvailable,
-} from '@roadiehq/backstage-plugin-github-pull-requests';
-import {
- EntityDependabotAlertsCard,
- isSecurityInsightsAvailable,
-} from '@roadiehq/backstage-plugin-security-insights';
-import {
- isPluginApplicableToEntity as isPagerDutyAvailable,
- EntityPagerDutyCard,
-} from '@backstage/plugin-pagerduty';
-import {
- EntityLastLighthouseAuditCard,
- isLighthouseAvailable,
-} from '@backstage/plugin-lighthouse';
-import { isCIsAvailable } from './CI';
-import { entityWarningContent } from './EntityWarning';
-import { isPRsAvailable } from './PullRequests';
-
-export const overviewContent = (
-
-
- {entityWarningContent}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- !isPRsAvailable(e)}>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Use `isArgocdAvailable` once its fixed */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Content/PullRequests.tsx b/packages/app/src/components/catalog/EntityPage/Content/PullRequests.tsx
deleted file mode 100644
index f85288cae8..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Content/PullRequests.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { type Entity } from '@backstage/catalog-model';
-import {
- EntityAzurePullRequestsContent,
- isAzureDevOpsAvailable,
-} from '@backstage/plugin-azure-devops';
-import { EntitySwitch } from '@backstage/plugin-catalog';
-import {
- EntityGitlabMergeRequestsTable,
- isGitlabAvailable,
-} from '@immobiliarelabs/backstage-plugin-gitlab';
-import Grid from '@mui/material/Grid';
-import {
- EntityGithubPullRequestsContent,
- isGithubPullRequestsAvailable,
-} from '@roadiehq/backstage-plugin-github-pull-requests';
-import React from 'react';
-
-const ifPRs: ((e: Entity) => boolean)[] = [
- isGithubPullRequestsAvailable,
- isGitlabAvailable,
- isAzureDevOpsAvailable,
-];
-
-export const isPRsAvailable = (e: Entity) => ifPRs.some(f => f(e));
-
-export const prContent = (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Content/SecurityInsights.tsx b/packages/app/src/components/catalog/EntityPage/Content/SecurityInsights.tsx
deleted file mode 100644
index 3ce67c9c30..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Content/SecurityInsights.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import Grid from '@mui/material/Grid';
-import {
- EntityGithubDependabotContent,
- EntitySecurityInsightsContent,
-} from '@roadiehq/backstage-plugin-security-insights';
-import React from 'react';
-
-export const securityContent = (
-
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Content/index.tsx b/packages/app/src/components/catalog/EntityPage/Content/index.tsx
deleted file mode 100644
index ff4cbc2073..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Content/index.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-export * from './CD';
-export * from './CI';
-export * from './Dependencies';
-export * from './Docs';
-export * from './ImageRegistry';
-export * from './Issues';
-export * from './Monitoring';
-export * from './Overview';
-export * from './PullRequests';
-export * from './SecurityInsights';
diff --git a/packages/app/src/components/catalog/EntityPage/Pages/Api.tsx b/packages/app/src/components/catalog/EntityPage/Pages/Api.tsx
deleted file mode 100644
index 033ab60154..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Pages/Api.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import {
- EntityApiDefinitionCard,
- EntityConsumingComponentsCard,
- EntityProvidingComponentsCard,
-} from '@backstage/plugin-api-docs';
-import {
- EntityAboutCard,
- EntityLayout,
- EntityLinksCard,
-} from '@backstage/plugin-catalog';
-import { EntityCatalogGraphCard } from '@backstage/plugin-catalog-graph';
-import Grid from '@mui/material/Grid';
-import React from 'react';
-import { entityWarningContent } from '../Content/EntityWarning';
-
-export const apiPage = (
-
-
-
-
- {entityWarningContent}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Pages/Component.tsx b/packages/app/src/components/catalog/EntityPage/Pages/Component.tsx
deleted file mode 100644
index dea2a70878..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Pages/Component.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import {
- EntityConsumedApisCard,
- EntityProvidedApisCard,
-} from '@backstage/plugin-api-docs';
-import {
- EntityLayout,
- EntitySwitch,
- isComponentType,
-} from '@backstage/plugin-catalog';
-import { EntityKubernetesContent } from '@backstage/plugin-kubernetes';
-import {
- EntityLighthouseContent,
- isLighthouseAvailable,
-} from '@backstage/plugin-lighthouse';
-import { TektonCI } from '@janus-idp/backstage-plugin-tekton';
-import { TopologyPage } from '@janus-idp/backstage-plugin-topology';
-import Grid from '@mui/material/Grid';
-import React from 'react';
-import {
- cdContent,
- ciContent,
- dependenciesContent,
- imageRegistry,
- isCIsAvailable,
- isImageRegistriesAvailable,
- isIssuesAvailable,
- isMonitoringAvailable,
- isPRsAvailable,
- issuesContent,
- monitoringContent,
- overviewContent,
- prContent,
- techdocsContent,
-} from '../Content';
-import { defaultEntityPage } from './DefaultEntity';
-
-const componentEntityPage = (componentType: 'service' | 'website') => (
-
-
- {overviewContent}
-
-
-
-
-
-
-
- {issuesContent}
-
-
-
- {prContent}
-
-
-
- {ciContent}
-
-
- {/* Use `isArgocdAvailable` once its fixed */}
-
- {cdContent}
-
-
-
-
-
-
-
-
-
-
-
- {imageRegistry}
-
-
-
- {monitoringContent}
-
-
-
-
-
-
- {componentType === 'service' && (
-
-
-
-
-
-
-
-
-
-
- )}
-
-
- {dependenciesContent}
-
-
-
- {techdocsContent}
-
-
-);
-
-export const componentPage = (
-
-
- {componentEntityPage('service')}
-
-
-
- {componentEntityPage('website')}
-
-
- {defaultEntityPage}
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Pages/DefaultEntity.tsx b/packages/app/src/components/catalog/EntityPage/Pages/DefaultEntity.tsx
deleted file mode 100644
index 20282c2b32..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Pages/DefaultEntity.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { EntityLayout } from '@backstage/plugin-catalog';
-import React from 'react';
-import { overviewContent, techdocsContent } from '../Content';
-
-export const defaultEntityPage = (
-
-
- {overviewContent}
-
-
-
- {techdocsContent}
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Pages/Domain.tsx b/packages/app/src/components/catalog/EntityPage/Pages/Domain.tsx
deleted file mode 100644
index d87730ef0f..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Pages/Domain.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import {
- EntityAboutCard,
- EntityHasSystemsCard,
- EntityLayout,
-} from '@backstage/plugin-catalog';
-import { EntityCatalogGraphCard } from '@backstage/plugin-catalog-graph';
-import Grid from '@mui/material/Grid';
-import React from 'react';
-import { entityWarningContent } from '../Content/EntityWarning';
-
-export const domainPage = (
-
-
-
-
- {entityWarningContent}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Pages/Group.tsx b/packages/app/src/components/catalog/EntityPage/Pages/Group.tsx
deleted file mode 100644
index f5664e2952..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Pages/Group.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { EntityLayout } from '@backstage/plugin-catalog';
-import {
- EntityGroupProfileCard,
- EntityMembersListCard,
- EntityOwnershipCard,
-} from '@backstage/plugin-org';
-import Grid from '@mui/material/Grid';
-import React from 'react';
-import { entityWarningContent } from '../Content/EntityWarning';
-
-export const groupPage = (
-
-
-
-
- {entityWarningContent}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Pages/Resource.tsx b/packages/app/src/components/catalog/EntityPage/Pages/Resource.tsx
deleted file mode 100644
index 3f7d51e33a..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Pages/Resource.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import {
- EntityAboutCard,
- EntityHasSystemsCard,
- EntityLayout,
- EntityLinksCard,
- EntitySwitch,
-} from '@backstage/plugin-catalog';
-import { EntityCatalogGraphCard } from '@backstage/plugin-catalog-graph';
-import Grid from '@mui/material/Grid';
-import React from 'react';
-
-import {
- ClusterAvailableResourceCard,
- ClusterContextProvider,
- ClusterInfoCard,
-} from '@janus-idp/backstage-plugin-ocm';
-import { isType } from '../../utils';
-import { entityWarningContent } from '../Content/EntityWarning';
-
-export const resourcePage = (
-
-
-
-
- {entityWarningContent}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Pages/System.tsx b/packages/app/src/components/catalog/EntityPage/Pages/System.tsx
deleted file mode 100644
index ef261b498f..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Pages/System.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import {
- RELATION_API_CONSUMED_BY,
- RELATION_API_PROVIDED_BY,
- RELATION_CONSUMES_API,
- RELATION_DEPENDENCY_OF,
- RELATION_DEPENDS_ON,
- RELATION_HAS_PART,
- RELATION_PART_OF,
- RELATION_PROVIDES_API,
-} from '@backstage/catalog-model';
-import { EntityHasApisCard } from '@backstage/plugin-api-docs';
-import {
- EntityAboutCard,
- EntityHasComponentsCard,
- EntityHasResourcesCard,
- EntityLayout,
- EntityLinksCard,
-} from '@backstage/plugin-catalog';
-import {
- Direction,
- EntityCatalogGraphCard,
-} from '@backstage/plugin-catalog-graph';
-import Grid from '@mui/material/Grid';
-import React from 'react';
-import { entityWarningContent } from '../Content/EntityWarning';
-
-export const systemPage = (
-
-
-
-
- {entityWarningContent}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Pages/User.tsx b/packages/app/src/components/catalog/EntityPage/Pages/User.tsx
deleted file mode 100644
index b4ec1f9c6d..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Pages/User.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { EntityLayout } from '@backstage/plugin-catalog';
-import {
- EntityOwnershipCard,
- EntityUserProfileCard,
-} from '@backstage/plugin-org';
-import Grid from '@mui/material/Grid';
-import React from 'react';
-import { entityWarningContent } from '../Content/EntityWarning';
-
-export const userPage = (
-
-
-
-
- {entityWarningContent}
-
-
-
-
-
-
-
-
-
-
-
-
-);
diff --git a/packages/app/src/components/catalog/EntityPage/Pages/index.tsx b/packages/app/src/components/catalog/EntityPage/Pages/index.tsx
deleted file mode 100644
index 6f03788200..0000000000
--- a/packages/app/src/components/catalog/EntityPage/Pages/index.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-export * from './Api';
-export * from './Component';
-export * from './DefaultEntity';
-export * from './Domain';
-export * from './Group';
-export * from './Resource';
-export * from './System';
-export * from './User';
diff --git a/packages/app/src/components/catalog/EntityPage/index.tsx b/packages/app/src/components/catalog/EntityPage/index.tsx
index a0179b656a..a882b7138e 100644
--- a/packages/app/src/components/catalog/EntityPage/index.tsx
+++ b/packages/app/src/components/catalog/EntityPage/index.tsx
@@ -1,27 +1,352 @@
-import { EntitySwitch, isKind } from '@backstage/plugin-catalog';
import React from 'react';
-
import {
- componentPage,
- defaultEntityPage,
- apiPage,
- groupPage,
- userPage,
- systemPage,
- domainPage,
- resourcePage,
-} from './Pages';
+ EntityApiDefinitionCard,
+ EntityConsumedApisCard,
+ EntityConsumingComponentsCard,
+ EntityHasApisCard,
+ EntityProvidedApisCard,
+ EntityProvidingComponentsCard,
+} from '@backstage/plugin-api-docs';
+import {
+ EntityAboutCard,
+ EntityDependsOnComponentsCard,
+ EntityDependsOnResourcesCard,
+ EntityHasComponentsCard,
+ EntityHasResourcesCard,
+ EntityHasSubcomponentsCard,
+ EntityHasSystemsCard,
+ EntityLayout,
+ EntityLinksCard,
+ EntityOrphanWarning,
+ EntityProcessingErrorsPanel,
+ EntityRelationWarning,
+ EntitySwitch,
+ hasCatalogProcessingErrors,
+ hasRelationWarnings,
+ isKind,
+ isOrphan,
+} from '@backstage/plugin-catalog';
+import tab from '../tab';
+import { hasLinks, isType } from '../utils';
+import {
+ Direction,
+ EntityCatalogGraphCard,
+} from '@backstage/plugin-catalog-graph';
+import {
+ EntityTechdocsContent,
+ isTechDocsAvailable,
+} from '@backstage/plugin-techdocs';
+import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
+import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
+import {
+ EntityGroupProfileCard,
+ EntityMembersListCard,
+ EntityOwnershipCard,
+ EntityUserProfileCard,
+} from '@backstage/plugin-org';
+import {
+ RELATION_API_CONSUMED_BY,
+ RELATION_API_PROVIDED_BY,
+ RELATION_CONSUMES_API,
+ RELATION_DEPENDENCY_OF,
+ RELATION_DEPENDS_ON,
+ RELATION_HAS_PART,
+ RELATION_PART_OF,
+ RELATION_PROVIDES_API,
+} from '@backstage/catalog-model';
+import Grid from '../Grid';
+
+// import {EntityGithubPullRequestsContent} from '@roadiehq/backstage-plugin-github-pull-requests';
export const entityPage = (
-
-
-
-
-
-
-
-
-
- {defaultEntityPage}
-
+
+ {tab({
+ path: '/',
+ title: 'Overview',
+ mountPoint: 'entity.page.overview',
+ children: (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ ),
+ })}
+
+ {tab({
+ path: '/topology',
+ title: 'Topology',
+ mountPoint: 'entity.page.topology',
+ })}
+
+ {tab({
+ path: '/issues',
+ title: 'Issues',
+ mountPoint: 'entity.page.issues',
+ })}
+
+ {tab({
+ path: '/pr',
+ title: 'Pull/Merge Requests',
+ mountPoint: 'entity.page.pull-requests',
+ // children: (
+ //
+ //
+ //
+ // )
+ })}
+
+ {tab({
+ path: '/ci',
+ title: 'CI',
+ mountPoint: 'entity.page.ci',
+ })}
+
+ {tab({
+ path: '/cd',
+ title: 'CD',
+ mountPoint: 'entity.page.cd',
+ })}
+
+ {tab({
+ path: '/kubernetes',
+ title: 'Kubernetes',
+ mountPoint: 'entity.page.kubernetes',
+ })}
+
+ {tab({
+ path: '/tekton',
+ title: 'Tekton',
+ mountPoint: 'entity.page.tekton',
+ })}
+
+ {tab({
+ path: '/image-registry',
+ title: 'Image Registry',
+ mountPoint: 'entity.page.image-registry',
+ })}
+
+ {tab({
+ path: '/monitoring',
+ title: 'Monitoring',
+ mountPoint: 'entity.page.monitoring',
+ })}
+
+ {tab({
+ path: '/lighthouse',
+ title: 'Lighthouse',
+ mountPoint: 'entity.page.lighthouse',
+ })}
+
+ {tab({
+ path: '/api',
+ title: 'Api',
+ mountPoint: 'entity.page.api',
+ if: e => isType('service')(e) && isKind('component')(e),
+ children: (
+
+ isType('service')(e) && isKind('component')(e)}
+ >
+
+
+
+
+
+
+
+
+ ),
+ })}
+
+ {tab({
+ path: '/dependencies',
+ title: 'Dependencies',
+ mountPoint: 'entity.page.dependencies',
+ if: isKind('component'),
+ children: (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+ })}
+
+ {tab({
+ path: '/docs',
+ title: 'Docs',
+ mountPoint: 'entity.page.docs',
+ if: isTechDocsAvailable,
+ children: (
+
+
+
+
+
+
+
+
+
+
+
+ ),
+ })}
+
+ {tab({
+ path: '/definition',
+ title: 'Definition',
+ mountPoint: 'entity.page.definition',
+ if: isKind('api'),
+ children: (
+
+
+
+
+
+
+
+ ),
+ })}
+
+ {tab({
+ path: '/diagram',
+ title: 'Diagram',
+ mountPoint: 'entity.page.diagram',
+ if: isKind('system'),
+ children: (
+
+
+
+
+
+
+
+ ),
+ })}
+
);
diff --git a/packages/app/src/components/catalog/Grid/Grid.tsx b/packages/app/src/components/catalog/Grid/Grid.tsx
new file mode 100644
index 0000000000..8e9ad36fbe
--- /dev/null
+++ b/packages/app/src/components/catalog/Grid/Grid.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import Box, { BoxProps } from '@mui/material/Box';
+import { makeStyles } from 'tss-react/mui';
+
+const useStyles = makeStyles()(theme => ({
+ grid: {
+ display: 'grid',
+ gridTemplateColumns: 'repeat(12, 1fr)',
+ gridGap: theme.spacing(3),
+ gridAutoFlow: 'dense',
+ },
+}));
+
+const Grid = ({
+ container = false,
+ item = true,
+ children,
+ ...props
+}: React.PropsWithChildren<
+ { container?: boolean; item?: boolean } & BoxProps
+>) => {
+ const { classes } = useStyles();
+
+ if (container) {
+ return (
+
+ {children}
+
+ );
+ }
+ if (item) {
+ return {children};
+ }
+ return null;
+};
+
+export default Grid;
diff --git a/packages/app/src/components/catalog/Grid/index.ts b/packages/app/src/components/catalog/Grid/index.ts
new file mode 100644
index 0000000000..3d919c9acb
--- /dev/null
+++ b/packages/app/src/components/catalog/Grid/index.ts
@@ -0,0 +1 @@
+export { default } from './Grid';
diff --git a/packages/app/src/components/catalog/tab/index.ts b/packages/app/src/components/catalog/tab/index.ts
new file mode 100644
index 0000000000..bd1dff4eb0
--- /dev/null
+++ b/packages/app/src/components/catalog/tab/index.ts
@@ -0,0 +1 @@
+export { default } from './tab';
diff --git a/packages/app/src/components/catalog/tab/tab.tsx b/packages/app/src/components/catalog/tab/tab.tsx
new file mode 100644
index 0000000000..bd940c427a
--- /dev/null
+++ b/packages/app/src/components/catalog/tab/tab.tsx
@@ -0,0 +1,54 @@
+import React from 'react';
+import getMountPointData from '../../../utils/dynamicUI/getMountPointData';
+import { EntityLayout, EntitySwitch } from '@backstage/plugin-catalog';
+import Grid from '../Grid';
+import { Entity } from '@backstage/catalog-model';
+
+export type TabProps = {
+ path: string;
+ title: string;
+ mountPoint: string;
+ if?: (e: Entity) => boolean;
+ children?: React.ReactNode;
+};
+
+const tab = ({
+ path,
+ title,
+ mountPoint,
+ children,
+ if: condition,
+}: TabProps) => (
+
+ (condition ? condition(e) : Boolean(children)) ||
+ getMountPointData(`${mountPoint}/cards`)
+ .flatMap(({ config }) => config.if)
+ .some(c => c(e))
+ }
+ >
+ {getMountPointData(`${mountPoint}/context`).reduce(
+ (acc, { component: Component }) => (
+ {acc}
+ ),
+
+ {children}
+ {getMountPointData(`${mountPoint}/cards`).map(
+ ({ component: Component, config }, index) => (
+
+
+
+
+
+
+
+ ),
+ )}
+ ,
+ )}
+
+);
+
+export default tab;
diff --git a/packages/app/src/components/catalog/utils.tsx b/packages/app/src/components/catalog/utils.tsx
index af4898acc9..ee4c683027 100644
--- a/packages/app/src/components/catalog/utils.tsx
+++ b/packages/app/src/components/catalog/utils.tsx
@@ -8,3 +8,8 @@ export const isType = (types: string | string[]) => (entity: Entity) => {
? entity?.spec?.type === types
: types.includes(entity.spec.type as string);
};
+export const hasAnnotation = (keys: string) => (entity: Entity) =>
+ Boolean(entity.metadata.annotations?.[keys]);
+
+export const hasLinks = (entity: Entity) =>
+ Boolean(entity.metadata.links?.length);
diff --git a/packages/app/src/utils/dynamicUI/extractDynamicConfig.ts b/packages/app/src/utils/dynamicUI/extractDynamicConfig.ts
index 537e73fb73..a34431b3bd 100644
--- a/packages/app/src/utils/dynamicUI/extractDynamicConfig.ts
+++ b/packages/app/src/utils/dynamicUI/extractDynamicConfig.ts
@@ -2,6 +2,7 @@ import { defaultConfigLoader } from '@backstage/core-app-api';
import {
DynamicModuleEntry,
RouteBinding,
+ ScalprumMountPointConfigRaw,
} from '../../components/DynamicRoot/DynamicRootContext';
type AppConfig = {
@@ -48,6 +49,7 @@ async function extractDynamicConfig() {
module: string;
importName: string;
mountPoint: string;
+ config?: ScalprumMountPointConfigRaw;
}[];
}>(
(acc, { data }) => {
diff --git a/packages/app/src/utils/dynamicUI/getMountPointData.ts b/packages/app/src/utils/dynamicUI/getMountPointData.ts
index ab1dcf4f6c..0e17888134 100644
--- a/packages/app/src/utils/dynamicUI/getMountPointData.ts
+++ b/packages/app/src/utils/dynamicUI/getMountPointData.ts
@@ -1,6 +1,9 @@
import { getScalprum } from '@scalprum/core';
+import { ScalprumMountPointConfig } from '../../components/DynamicRoot/DynamicRootContext';
-function getMountPointData(mountPoint: string): T[] {
+function getMountPointData(
+ mountPoint: string,
+): { config: ScalprumMountPointConfig; component: T }[] {
return getScalprum().api.mountPoints?.[mountPoint] ?? [];
}