diff --git a/plugins/sonarqube/dev/index.tsx b/plugins/sonarqube/dev/index.tsx
index 812a5585d4..830d8aa575 100644
--- a/plugins/sonarqube/dev/index.tsx
+++ b/plugins/sonarqube/dev/index.tsx
@@ -14,7 +14,149 @@
* limitations under the License.
*/
+import { Entity } from '@backstage/catalog-model';
+import {
+ Content,
+ createPlugin,
+ createRouteRef,
+ Header,
+ Page,
+} from '@backstage/core';
import { createDevApp } from '@backstage/dev-utils';
-import { plugin } from '../src/plugin';
+import { Grid } from '@material-ui/core';
+import React from 'react';
+import { SonarQubeCard } from '../src';
+import { FindingSummary, SonarQubeApi, sonarQubeApiRef } from '../src/api';
+import { SONARQUBE_PROJECT_KEY_ANNOTATION } from '../src/components/useProjectKey';
-createDevApp().registerPlugin(plugin).render();
+createDevApp()
+ .registerApi({
+ api: sonarQubeApiRef,
+ deps: {},
+ factory: () =>
+ ({
+ getFindingSummary: async componentKey => {
+ switch (componentKey) {
+ case 'error':
+ throw new Error('Error!');
+
+ case 'never':
+ return new Promise(() => {});
+
+ case 'not-computed':
+ return {
+ lastAnalysis: new Date().toISOString(),
+ metrics: {
+ bugs: '0',
+ reliability_rating: '1.0',
+ vulnerabilities: '0',
+ security_rating: '1.0',
+ code_smells: '0',
+ sqale_rating: '1.0',
+ coverage: '0.0',
+ duplicated_lines_density: '0.0',
+ },
+ projectUrl: `/#${componentKey}`,
+ getIssuesUrl: i => `/#${componentKey}/issues/${i}`,
+ getComponentMeasuresUrl: i => `/#${componentKey}/measures/${i}`,
+ } as FindingSummary;
+
+ case 'failed':
+ return {
+ lastAnalysis: new Date().toISOString(),
+ metrics: {
+ alert_status: 'FAILED',
+ bugs: '4',
+ reliability_rating: '2.0',
+ vulnerabilities: '18',
+ security_rating: '3.0',
+ code_smells: '22',
+ sqale_rating: '5.0',
+ coverage: '15.7',
+ duplicated_lines_density: '15.6',
+ },
+ projectUrl: `/#${componentKey}`,
+ getIssuesUrl: i => `/#${componentKey}/issues/${i}`,
+ getComponentMeasuresUrl: i => `/#${componentKey}/measures/${i}`,
+ } as FindingSummary;
+
+ case 'passed':
+ return {
+ lastAnalysis: new Date().toISOString(),
+ metrics: {
+ alert_status: 'OK',
+ bugs: '0',
+ reliability_rating: '1.0',
+ vulnerabilities: '0',
+ security_rating: '1.0',
+ code_smells: '0',
+ sqale_rating: '1.0',
+ coverage: '100.0',
+ duplicated_lines_density: '0.0',
+ },
+ projectUrl: `/#${componentKey}`,
+ getIssuesUrl: i => `/#${componentKey}/issues/${i}`,
+ getComponentMeasuresUrl: i => `/#${componentKey}/measures/${i}`,
+ } as FindingSummary;
+
+ default:
+ return undefined;
+ }
+ },
+ } as SonarQubeApi),
+ })
+ .registerPlugin(
+ createPlugin({
+ id: 'defectdojo-demo',
+ register({ router }) {
+ const entity = (name?: string) =>
+ ({
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ annotations: {
+ [SONARQUBE_PROJECT_KEY_ANNOTATION]: name,
+ },
+ name: name,
+ },
+ } as Entity);
+
+ const ExamplePage = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ router.addRoute(
+ createRouteRef({ path: '/', title: 'SonarQube' }),
+ ExamplePage,
+ );
+ },
+ }),
+ )
+ .render();
diff --git a/plugins/sonarqube/src/api/SonarQubeApi.ts b/plugins/sonarqube/src/api/SonarQubeApi.ts
new file mode 100644
index 0000000000..5734f9a3ca
--- /dev/null
+++ b/plugins/sonarqube/src/api/SonarQubeApi.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createApiRef } from '@backstage/core';
+import { MetricKey, SonarUrlProcessorFunc } from './types';
+
+/**
+ * Define a type to make sure that all metrics are used
+ */
+export type Metrics = {
+ [key in MetricKey]: string | undefined;
+};
+
+export interface FindingSummary {
+ lastAnalysis: string;
+ metrics: Metrics;
+ projectUrl: string;
+ getIssuesUrl: SonarUrlProcessorFunc;
+ getComponentMeasuresUrl: SonarUrlProcessorFunc;
+}
+
+export const sonarQubeApiRef = createApiRef({
+ id: 'plugin.sonarqube.service',
+ description: 'Used by the SonarQube plugin to make requests',
+});
+
+export type SonarQubeApi = {
+ getFindingSummary(componentKey?: string): Promise;
+};
diff --git a/plugins/sonarqube/src/api/index.test.ts b/plugins/sonarqube/src/api/SonarQubeClient.test.ts
similarity index 96%
rename from plugins/sonarqube/src/api/index.test.ts
rename to plugins/sonarqube/src/api/SonarQubeClient.test.ts
index 2d96fc2135..a3ae24de52 100644
--- a/plugins/sonarqube/src/api/index.test.ts
+++ b/plugins/sonarqube/src/api/SonarQubeClient.test.ts
@@ -18,12 +18,12 @@ import { UrlPatternDiscovery } from '@backstage/core';
import { msw } from '@backstage/test-utils';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
-import { FindingSummary, SonarQubeApi } from './index';
+import { FindingSummary, SonarQubeClient } from './index';
import { ComponentWrapper, MeasuresWrapper } from './types';
const server = setupServer();
-describe('SonarQubeApi', () => {
+describe('SonarQubeClient', () => {
msw.setupDefaultHandlers(server);
const mockBaseUrl = 'http://backstage:9191/api/proxy';
@@ -111,7 +111,7 @@ describe('SonarQubeApi', () => {
it('should report finding summary', async () => {
setupHandlers();
- const client = new SonarQubeApi({ discoveryApi });
+ const client = new SonarQubeClient({ discoveryApi });
const summary = await client.getFindingSummary('our-service');
expect(summary).toEqual(
@@ -142,7 +142,7 @@ describe('SonarQubeApi', () => {
it('should report finding summary (custom baseUrl)', async () => {
setupHandlers();
- const client = new SonarQubeApi({
+ const client = new SonarQubeClient({
discoveryApi,
baseUrl: 'http://a.instance.local',
});
diff --git a/plugins/sonarqube/src/api/SonarQubeClient.ts b/plugins/sonarqube/src/api/SonarQubeClient.ts
new file mode 100644
index 0000000000..893f32b0a6
--- /dev/null
+++ b/plugins/sonarqube/src/api/SonarQubeClient.ts
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { DiscoveryApi } from '@backstage/core';
+import fetch from 'cross-fetch';
+import { FindingSummary, Metrics, SonarQubeApi } from './SonarQubeApi';
+import { ComponentWrapper, MeasuresWrapper } from './types';
+
+export class SonarQubeClient implements SonarQubeApi {
+ discoveryApi: DiscoveryApi;
+ baseUrl: string;
+
+ constructor({
+ discoveryApi,
+ baseUrl = 'https://sonarcloud.io/',
+ }: {
+ discoveryApi: DiscoveryApi;
+ baseUrl?: string;
+ }) {
+ this.discoveryApi = discoveryApi;
+ this.baseUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
+ }
+
+ private async callApi(path: string): Promise {
+ const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/sonarqube`;
+ const response = await fetch(`${apiUrl}/${path}`);
+ if (response.status === 200) {
+ return (await response.json()) as T;
+ }
+ return undefined;
+ }
+
+ async getFindingSummary(
+ componentKey?: string,
+ ): Promise {
+ if (!componentKey) {
+ return undefined;
+ }
+
+ const component = await this.callApi(
+ `components/show?component=${componentKey}`,
+ );
+ if (!component) {
+ return undefined;
+ }
+
+ const metrics: Metrics = {
+ alert_status: undefined,
+ bugs: undefined,
+ reliability_rating: undefined,
+ vulnerabilities: undefined,
+ security_rating: undefined,
+ code_smells: undefined,
+ sqale_rating: undefined,
+ coverage: undefined,
+ duplicated_lines_density: undefined,
+ };
+
+ const measures = await this.callApi(
+ `measures/search?projectKeys=${componentKey}&metricKeys=${Object.keys(
+ metrics,
+ ).join(',')}`,
+ );
+ if (!measures) {
+ return undefined;
+ }
+
+ measures.measures
+ .filter(m => m.component === componentKey)
+ .forEach(m => {
+ metrics[m.metric] = m.value;
+ });
+
+ return {
+ lastAnalysis: component.component.analysisDate,
+ metrics,
+ projectUrl: `${this.baseUrl}dashboard?id=${componentKey}`,
+ getIssuesUrl: identifier =>
+ `${
+ this.baseUrl
+ }project/issues?id=${componentKey}&types=${identifier.toUpperCase()}&resolved=false`,
+ getComponentMeasuresUrl: (identifier: string) =>
+ `${
+ this.baseUrl
+ }component_measures?id=${componentKey}&metric=${identifier.toLowerCase()}&resolved=false&view=list`,
+ };
+ }
+}
diff --git a/plugins/sonarqube/src/api/index.ts b/plugins/sonarqube/src/api/index.ts
index f2c616f53c..8442465dee 100644
--- a/plugins/sonarqube/src/api/index.ts
+++ b/plugins/sonarqube/src/api/index.ts
@@ -14,112 +14,6 @@
* limitations under the License.
*/
-import { createApiRef, DiscoveryApi } from '@backstage/core';
-import fetch from 'cross-fetch';
-import {
- ComponentWrapper,
- MeasuresWrapper,
- MetricKey,
- SonarUrlProcessorFunc,
-} from './types';
-
-/**
- * Define a type to make sure that all metrics are used
- */
-type Metrics = {
- [key in MetricKey]: string | undefined;
-};
-
-export interface FindingSummary {
- lastAnalysis: string;
- metrics: Metrics;
- projectUrl: string;
- getIssuesUrl: SonarUrlProcessorFunc;
- getComponentMeasuresUrl: SonarUrlProcessorFunc;
-}
-
-export const sonarQubeApiRef = createApiRef({
- id: 'plugin.sonarqube.service',
- description: 'Used by the SonarQube plugin to make requests',
-});
-
-export class SonarQubeApi {
- discoveryApi: DiscoveryApi;
- baseUrl: string;
-
- constructor({
- discoveryApi,
- baseUrl = 'https://sonarcloud.io/',
- }: {
- discoveryApi: DiscoveryApi;
- baseUrl?: string;
- }) {
- this.discoveryApi = discoveryApi;
- this.baseUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
- }
-
- private async callApi(path: string): Promise {
- const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/sonarqube`;
- const response = await fetch(`${apiUrl}/${path}`);
- if (response.status === 200) {
- return (await response.json()) as T;
- }
- return undefined;
- }
-
- async getFindingSummary(
- componentKey?: string,
- ): Promise {
- if (!componentKey) {
- return undefined;
- }
-
- const component = await this.callApi(
- `components/show?component=${componentKey}`,
- );
- if (!component) {
- return undefined;
- }
-
- const metrics: Metrics = {
- alert_status: undefined,
- bugs: undefined,
- reliability_rating: undefined,
- vulnerabilities: undefined,
- security_rating: undefined,
- code_smells: undefined,
- sqale_rating: undefined,
- coverage: undefined,
- duplicated_lines_density: undefined,
- };
-
- const measures = await this.callApi(
- `measures/search?projectKeys=${componentKey}&metricKeys=${Object.keys(
- metrics,
- ).join(',')}`,
- );
- if (!measures) {
- return undefined;
- }
-
- measures.measures
- .filter(m => m.component === componentKey)
- .forEach(m => {
- metrics[m.metric] = m.value;
- });
-
- return {
- lastAnalysis: component.component.analysisDate,
- metrics,
- projectUrl: `${this.baseUrl}dashboard?id=${componentKey}`,
- getIssuesUrl: identifier =>
- `${
- this.baseUrl
- }project/issues?id=${componentKey}&types=${identifier.toUpperCase()}&resolved=false`,
- getComponentMeasuresUrl: (identifier: string) =>
- `${
- this.baseUrl
- }component_measures?id=${componentKey}&metric=${identifier.toLowerCase()}&resolved=false&view=list`,
- };
- }
-}
+export type { Metrics, FindingSummary, SonarQubeApi } from './SonarQubeApi';
+export { sonarQubeApiRef } from './SonarQubeApi';
+export { SonarQubeClient } from './SonarQubeClient';
diff --git a/plugins/sonarqube/src/plugin.ts b/plugins/sonarqube/src/plugin.ts
index 154d4ee3b8..f8b8cafc5c 100644
--- a/plugins/sonarqube/src/plugin.ts
+++ b/plugins/sonarqube/src/plugin.ts
@@ -20,7 +20,7 @@ import {
createPlugin,
discoveryApiRef,
} from '@backstage/core';
-import { SonarQubeApi, sonarQubeApiRef } from './api';
+import { sonarQubeApiRef, SonarQubeClient } from './api';
export const plugin = createPlugin({
id: 'sonarqube',
@@ -29,7 +29,7 @@ export const plugin = createPlugin({
api: sonarQubeApiRef,
deps: { configApi: configApiRef, discoveryApi: discoveryApiRef },
factory: ({ configApi, discoveryApi }) =>
- new SonarQubeApi({
+ new SonarQubeClient({
discoveryApi,
baseUrl: configApi.getOptionalString('sonarQube.baseUrl'),
}),