Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/sonarqube-entity-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@openchoreo/backstage-portal-app': patch
'backend': patch
---

Add SonarQube community plugin integration — shows a Code Quality tab on entity pages when the `sonarqube.org/project-key` annotation is present. Wires up `@backstage-community/plugin-sonarqube-backend` in the backend and `EntitySonarQubeContentPage` in the frontend.
7 changes: 7 additions & 0 deletions app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ integrations:
# username: ${JENKINS_USERNAME}
# apiKey: ${JENKINS_API_KEY}

# SonarQube Configuration
# Uncomment and configure in app-config.local.yaml to enable code quality metrics on entity pages.
# Requires the `sonarqube.org/project-key` annotation on catalog entities to show the tab.
# sonarqube:
# baseUrl: ${SONARQUBE_BASE_URL} # e.g. https://sonarqube.example.com (no trailing slash)
# apiKey: ${SONARQUBE_API_KEY} # User token with at least Browse permission on the project

proxy:
### Example for how to add a proxy endpoint for the frontend.
### A typical reason to do this is to handle HTTPS and CORS for internal services.
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"build-image": "docker buildx build ../.. -f Dockerfile --tag openchoreo-ui:local --load"
},
"dependencies": {
"@backstage-community/plugin-sonarqube-backend": "^1.1.1",
"@backstage/backend-defaults": "^0.17.1",
"@backstage/backend-plugin-api": "^1.9.1",
"@backstage/config": "^1.3.8",
Expand All @@ -41,6 +42,7 @@
"pg": "8.16.3"
},
"devDependencies": {
"@backstage/backend-test-utils": "^1.11.5",
"@backstage/cli": "^0.36.2",
"@types/cookie-parser": "1.4.8",
"@types/express-session": "1.18.2"
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { createBackend } from '@backstage/backend-defaults';
import { portalBackendFeatures } from '@openchoreo/backstage-portal-backend';
import { conditionalSonarQube } from './sonarqube';

// Guest mode: when OpenChoreo auth is explicitly disabled, the portal has no
// IDP and needs guest sign-in plus an open default auth policy. These feed the
Expand All @@ -28,6 +29,8 @@ const backend = createBackend();
backend.add(portalBackendFeatures);

// External CI Platform Integrations
// SonarQube: Proxies metrics API. Conditionally registers only if 'sonarqube' config is present.
backend.add(conditionalSonarQube);
// GitLab: Requires integrations.gitlab config at startup. Uncomment after configuring in app-config.local.yaml
// For production, config is in app-config.production.yaml with Helm-injected env vars
// backend.add(import('@immobiliarelabs/backstage-plugin-gitlab-backend'));
Expand Down
16 changes: 16 additions & 0 deletions packages/backend/src/sonarqube.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { startTestBackend, mockServices } from '@backstage/backend-test-utils';
import { conditionalSonarQube } from './sonarqube';

describe('conditionalSonarQube backend module', () => {
it('should start successfully without a sonarqube configuration section', async () => {
// The test will fail (promise rejects) if the feature loader crashes.
const backend = await startTestBackend({
features: [
conditionalSonarQube,
mockServices.rootConfig.factory({ data: {} }), // empty configuration (no 'sonarqube')
],
});

expect(backend).toBeDefined();
});
});
26 changes: 26 additions & 0 deletions packages/backend/src/sonarqube.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
createBackendFeatureLoader,
coreServices,
} from '@backstage/backend-plugin-api';

/**
* SonarQube backend feature loader.
* Conditionally registers the SonarQube plugin only if 'sonarqube' config is present.
*/
export const conditionalSonarQube = createBackendFeatureLoader({
deps: {
config: coreServices.rootConfig,
logger: coreServices.rootLogger,
},
*loader({ config, logger }) {
if (config.getOptionalConfig('sonarqube')) {
// Use require() instead of import() to avoid ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG in Jest
const plugin = require('@backstage-community/plugin-sonarqube-backend');
yield plugin.default || plugin;
} else {
logger.info(
'SonarQube configuration is missing, skipping plugin-sonarqube-backend registration.',
);
}
},
});
2 changes: 2 additions & 0 deletions packages/portal-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
"dependencies": {
"@backstage-community/plugin-github-actions": "^0.20.0",
"@backstage-community/plugin-jenkins": "^0.28.0",
"@backstage-community/plugin-sonarqube": "^1.1.0",
"@backstage-community/plugin-sonarqube-react": "^1.1.0",
"@backstage/catalog-client": "^1.15.1",
"@backstage/catalog-model": "^1.9.0",
"@backstage/config": "^1.3.8",
Expand Down
11 changes: 11 additions & 0 deletions packages/portal-app/src/apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import {
perchAgentApiRef,
PerchAgentClient,
} from '@openchoreo/backstage-plugin-openchoreo-portal-assistant';
import { SonarQubeClient } from '@backstage-community/plugin-sonarqube';
import { sonarQubeApiRef } from '@backstage-community/plugin-sonarqube-react';

export const apis: AnyApiFactory[] = [
createApiFactory({
Expand Down Expand Up @@ -146,4 +148,13 @@ export const apis: AnyApiFactory[] = [
factory: ({ discoveryApi, fetchApi }) =>
new PerchAgentClient({ discoveryApi, fetchApi }),
}),
createApiFactory({
api: sonarQubeApiRef,
deps: {
discoveryApi: discoveryApiRef,
fetchApi: fetchApiRef,
},
factory: ({ discoveryApi, fetchApi }) =>
new SonarQubeClient({ discoveryApi, fetchApi }),
}),
];
20 changes: 20 additions & 0 deletions packages/portal-app/src/components/catalog/EntityPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ import { WorkflowsOrExternalCICard } from './WorkflowsOrExternalCICard';
import { EntityJenkinsContent } from '@backstage-community/plugin-jenkins';
import { EntityGithubActionsContent } from '@backstage-community/plugin-github-actions';
import { EntityGitlabContent } from '@immobiliarelabs/backstage-plugin-gitlab';
import { EntitySonarQubeContentPage } from '@backstage-community/plugin-sonarqube';
import { isSonarQubeAvailable } from '@backstage-community/plugin-sonarqube-react';

// Wires perch's per-row assistant button into the observability log
// tables via the plugin's render-prop slot. Lives here (not inside the
Expand Down Expand Up @@ -450,6 +452,15 @@ const ServiceEntityPage = () => {
>
<EntityGitlabContent />
</EntityLayout.Route>

{/* SonarQube code quality tab — only shown when annotation is present */}
<EntityLayout.Route
path="/sonarqube"
title="Code Quality"
if={isSonarQubeAvailable}
>
<EntitySonarQubeContentPage />
</EntityLayout.Route>
</EntityLayoutWithDelete>
);
};
Expand Down Expand Up @@ -573,6 +584,15 @@ const GenericComponentEntityPage = () => {
>
<EntityGitlabContent />
</EntityLayout.Route>

{/* SonarQube code quality tab — only shown when annotation is present */}
<EntityLayout.Route
path="/sonarqube"
title="Code Quality"
if={isSonarQubeAvailable}
>
<EntitySonarQubeContentPage />
</EntityLayout.Route>
</EntityLayoutWithDelete>
);
};
Expand Down
Loading
Loading