From da0a79c3b8c64b97bc08fa3db797486e81684fa8 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Fri, 30 Oct 2020 09:52:39 +0100 Subject: [PATCH] feat: add sonarqube plugin (#3160) --- .../well-known-annotations.md | 16 ++ microsite/data/plugins/sonarqube.yaml | 9 + microsite/static/img/sonarqube-icon.svg | 41 ++++ plugins/sonarqube/.eslintrc.js | 3 + plugins/sonarqube/README.md | 95 ++++++++ plugins/sonarqube/dev/index.tsx | 20 ++ plugins/sonarqube/docs/sonar-card.png | Bin 0 -> 75595 bytes plugins/sonarqube/package.json | 52 ++++ plugins/sonarqube/src/api/index.test.ts | 161 ++++++++++++ plugins/sonarqube/src/api/index.ts | 110 +++++++++ plugins/sonarqube/src/api/types.ts | 55 +++++ .../components/SonarQubeCard/Percentage.tsx | 45 ++++ .../src/components/SonarQubeCard/Rating.tsx | 112 +++++++++ .../components/SonarQubeCard/RatingCard.tsx | 78 ++++++ .../SonarQubeCard/SonarQubeCard.tsx | 230 ++++++++++++++++++ .../src/components/SonarQubeCard/Value.tsx | 34 +++ .../src/components/SonarQubeCard/index.ts | 17 ++ plugins/sonarqube/src/components/index.ts | 17 ++ .../sonarqube/src/components/useProjectKey.ts | 23 ++ plugins/sonarqube/src/index.ts | 18 ++ plugins/sonarqube/src/plugin.test.ts | 23 ++ plugins/sonarqube/src/plugin.ts | 38 +++ plugins/sonarqube/src/setupTests.ts | 17 ++ 23 files changed, 1214 insertions(+) create mode 100644 microsite/data/plugins/sonarqube.yaml create mode 100644 microsite/static/img/sonarqube-icon.svg create mode 100644 plugins/sonarqube/.eslintrc.js create mode 100644 plugins/sonarqube/README.md create mode 100644 plugins/sonarqube/dev/index.tsx create mode 100644 plugins/sonarqube/docs/sonar-card.png create mode 100644 plugins/sonarqube/package.json create mode 100644 plugins/sonarqube/src/api/index.test.ts create mode 100644 plugins/sonarqube/src/api/index.ts create mode 100644 plugins/sonarqube/src/api/types.ts create mode 100644 plugins/sonarqube/src/components/SonarQubeCard/Percentage.tsx create mode 100644 plugins/sonarqube/src/components/SonarQubeCard/Rating.tsx create mode 100644 plugins/sonarqube/src/components/SonarQubeCard/RatingCard.tsx create mode 100644 plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx create mode 100644 plugins/sonarqube/src/components/SonarQubeCard/Value.tsx create mode 100644 plugins/sonarqube/src/components/SonarQubeCard/index.ts create mode 100644 plugins/sonarqube/src/components/index.ts create mode 100644 plugins/sonarqube/src/components/useProjectKey.ts create mode 100644 plugins/sonarqube/src/index.ts create mode 100644 plugins/sonarqube/src/plugin.test.ts create mode 100644 plugins/sonarqube/src/plugin.ts create mode 100644 plugins/sonarqube/src/setupTests.ts diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md index 40a4ca3ea1..47b1d5885d 100644 --- a/docs/features/software-catalog/well-known-annotations.md +++ b/docs/features/software-catalog/well-known-annotations.md @@ -173,6 +173,22 @@ The value of these annotations are the corresponding attributes that were found when ingestion the entity from LDAP. Not all of them may be present, depending on what attributes that the server presented at ingestion time. +### sonarqube.org/project-key + +```yaml +# Example: +metadata: + annotations: + sonarqube.org/project-key: pump-station +``` + +The value of this annotation is the project key of a +[SonarQube](https://sonarqube.org) or [SonarCloud](https://sonarcloud.io) +project within your organization. + +Specifying this annotation may enable SonarQube related features in Backstage +for that entity. + ## Deprecated Annotations The following annotations are deprecated, and only listed here to aid in diff --git a/microsite/data/plugins/sonarqube.yaml b/microsite/data/plugins/sonarqube.yaml new file mode 100644 index 0000000000..a2982a4a58 --- /dev/null +++ b/microsite/data/plugins/sonarqube.yaml @@ -0,0 +1,9 @@ +--- +title: SonarQube +author: SDA SE +authorUrl: https://sda.se/ +category: Quality +description: Components to display code quality metrics from SonarCloud and SonarQube. +documentation: https://github.com/spotify/backstage/blob/master/plugins/sonarqube/README.md +iconUrl: img/sonarqube-icon.svg +npmPackageName: '@backstage/plugin-sonarqube' diff --git a/microsite/static/img/sonarqube-icon.svg b/microsite/static/img/sonarqube-icon.svg new file mode 100644 index 0000000000..d3de342b38 --- /dev/null +++ b/microsite/static/img/sonarqube-icon.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/sonarqube/.eslintrc.js b/plugins/sonarqube/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/sonarqube/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/sonarqube/README.md b/plugins/sonarqube/README.md new file mode 100644 index 0000000000..3d3c1f725b --- /dev/null +++ b/plugins/sonarqube/README.md @@ -0,0 +1,95 @@ +# SonarQube Plugin + +The SonarQube Plugin displays code statistics from [SonarCloud](https://sonarcloud.io) or [SonarQube](https://sonarqube.com). + +![Sonar Card](./docs/sonar-card.png) + +## Getting Started + +1. Install the SonarQube Plugin: + +```bash +yarn add @backstage/plugin-sonarqube +``` + +2. Add plugin to the app: + +```js +// packages/app/src/plugins.ts + +export { plugin as SonarQube } from '@backstage/plugin-sonarqube'; +``` + +3. Add the `SonarQubeCard` to the EntityPage: + +```jsx +// packages/app/src/components/catalog/EntityPage.tsx + +import { SonarQubeCard } from '@backstage/plugin-sonarqube'; + +const OverviewContent = ({ entity }: { entity: Entity }) => ( + + // ... + + + + // ... + +); +``` + +4. Add the proxy config: + +**SonarCloud** + +```yaml +// app-config.yaml + +proxy: + '/sonarqube': + target: https://sonarcloud.io/api + allowedMethods: ['GET'] + headers: + Authorization: + # Content: 'Basic base64(":")' <-- note the trailing ':' + # Example: Basic bXktYXBpLWtleTo= + $env: SONARQUBE_AUTH_HEADER +``` + +**SonarQube** + +```yaml +// app-config.yaml + +proxy: + '/sonarqube': + target: https://your.sonarqube.instance.com/api + allowedMethods: ['GET'] + headers: + Authorization: + # Content: 'Basic base64(":")' <-- note the trailing ':' + # Example: Basic bXktYXBpLWtleTo= + $env: SONARQUBE_AUTH_HEADER + +sonarQube: + baseUrl: https://your.sonarqube.instance.com +``` + +5. Get and provide `SONARQUBE_AUTH_HEADER` as env variable (https://sonarcloud.io/account/security or https://docs.sonarqube.org/latest/user-guide/user-token/) + +6. Add the `sonarqube.org/project-key` annotation to your component-info.yaml file: + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage + description: | + Backstage is an open-source developer portal that puts the developer experience first. + annotations: + sonarqube.org/project-key: YOUR_PROJECT_KEY +spec: + type: library + owner: Spotify + lifecycle: experimental +``` diff --git a/plugins/sonarqube/dev/index.tsx b/plugins/sonarqube/dev/index.tsx new file mode 100644 index 0000000000..812a5585d4 --- /dev/null +++ b/plugins/sonarqube/dev/index.tsx @@ -0,0 +1,20 @@ +/* + * 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 { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; + +createDevApp().registerPlugin(plugin).render(); diff --git a/plugins/sonarqube/docs/sonar-card.png b/plugins/sonarqube/docs/sonar-card.png new file mode 100644 index 0000000000000000000000000000000000000000..cbab02edd63b1598b05981bdc0d42e506b97569a GIT binary patch literal 75595 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�&QZgZR|qRWnyVNSL~NZ7*3m}bwjs4*t?tWPY1?i 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 { + msw.setupDefaultHandlers(server); + + const mockBaseUrl = 'http://backstage:9191/api/proxy'; + const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl); + + const setupHandlers = () => { + server.use( + rest.get(`${mockBaseUrl}/sonarqube/components/show`, (req, res, ctx) => { + expect(req.url.searchParams.toString()).toBe('component=our-service'); + return res( + ctx.json({ + component: { + analysisDate: '2020-01-01T00:00:00Z', + }, + } as ComponentWrapper), + ); + }), + ); + + server.use( + rest.get(`${mockBaseUrl}/sonarqube/measures/search`, (req, res, ctx) => { + expect(req.url.searchParams.toString()).toBe( + 'projectKeys=our-service&metricKeys=alert_status%2Cbugs%2Creliability_rating%2Cvulnerabilities%2Csecurity_rating%2Ccode_smells%2Csqale_rating%2Ccoverage%2Cduplicated_lines_density', + ); + return res( + ctx.json({ + measures: [ + { + metric: 'alert_status', + value: 'OK', + component: 'our-service', + }, + { + metric: 'alert_status', + value: 'ERROR', + component: 'other-service', + }, + { + metric: 'bugs', + value: '2', + component: 'our-service', + }, + { + metric: 'reliability_rating', + value: '3.0', + component: 'our-service', + }, + { + metric: 'vulnerabilities', + value: '4', + component: 'our-service', + }, + { + metric: 'security_rating', + value: '1.0', + component: 'our-service', + }, + { + metric: 'code_smells', + value: '100', + component: 'our-service', + }, + { + metric: 'sqale_rating', + value: '2.0', + component: 'our-service', + }, + { + metric: 'coverage', + value: '55.5', + component: 'our-service', + }, + { + metric: 'duplicated_lines_density', + value: '1.0', + component: 'our-service', + }, + ], + } as MeasuresWrapper), + ); + }), + ); + }; + + it('should report finding summary', async () => { + setupHandlers(); + + const client = new SonarQubeApi({ discoveryApi }); + + const summary = await client.getFindingSummary('our-service'); + + expect(summary).toEqual({ + lastAnalysis: '2020-01-01T00:00:00Z', + metrics: { + alert_status: 'OK', + bugs: '2', + reliability_rating: '3.0', + vulnerabilities: '4', + security_rating: '1.0', + code_smells: '100', + sqale_rating: '2.0', + coverage: '55.5', + duplicated_lines_density: '1.0', + }, + projectUrl: 'https://sonarcloud.io/dashboard?id=our-service', + } as FindingSummary); + }); + + it('should report finding summary (custom baseUrl)', async () => { + setupHandlers(); + + const client = new SonarQubeApi({ + discoveryApi, + baseUrl: 'http://a.instance.local', + }); + + const summary = await client.getFindingSummary('our-service'); + + expect(summary).toEqual({ + lastAnalysis: '2020-01-01T00:00:00Z', + metrics: { + alert_status: 'OK', + bugs: '2', + reliability_rating: '3.0', + vulnerabilities: '4', + security_rating: '1.0', + code_smells: '100', + sqale_rating: '2.0', + coverage: '55.5', + duplicated_lines_density: '1.0', + }, + projectUrl: 'http://a.instance.local/dashboard?id=our-service', + } as FindingSummary); + }); +}); diff --git a/plugins/sonarqube/src/api/index.ts b/plugins/sonarqube/src/api/index.ts new file mode 100644 index 0000000000..01f6f74fe3 --- /dev/null +++ b/plugins/sonarqube/src/api/index.ts @@ -0,0 +1,110 @@ +/* + * 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, DiscoveryApi } from '@backstage/core'; +import fetch from 'cross-fetch'; +import { ComponentWrapper, MeasuresWrapper, MetricKey } 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; +} + +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}`, + }; + } +} diff --git a/plugins/sonarqube/src/api/types.ts b/plugins/sonarqube/src/api/types.ts new file mode 100644 index 0000000000..17539070cb --- /dev/null +++ b/plugins/sonarqube/src/api/types.ts @@ -0,0 +1,55 @@ +/* + * 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. + */ + +export interface ComponentWrapper { + component: Component; +} + +export interface Component { + analysisDate: string; +} + +export interface MeasuresWrapper { + measures: Measure[]; +} + +export type MetricKey = + // alert status + | 'alert_status' + + // bugs and rating (-> reliability) + | 'bugs' + | 'reliability_rating' + + // vulnerabilities and rating (-> security) + | 'vulnerabilities' + | 'security_rating' + + // code smells and rating (-> maintainability) + | 'code_smells' + | 'sqale_rating' + + // coverage + | 'coverage' + + // duplicated lines + | 'duplicated_lines_density'; + +export interface Measure { + metric: MetricKey; + value: string; + component: string; +} diff --git a/plugins/sonarqube/src/components/SonarQubeCard/Percentage.tsx b/plugins/sonarqube/src/components/SonarQubeCard/Percentage.tsx new file mode 100644 index 0000000000..9d1fc415ff --- /dev/null +++ b/plugins/sonarqube/src/components/SonarQubeCard/Percentage.tsx @@ -0,0 +1,45 @@ +/* + * 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 { BackstageTheme } from '@backstage/theme'; +import { makeStyles } from '@material-ui/core/styles'; +import { useTheme } from '@material-ui/styles'; +import { Circle } from 'rc-progress'; +import React from 'react'; + +const useStyles = makeStyles((theme: BackstageTheme) => ({ + root: { + height: theme.spacing(3), + width: theme.spacing(3), + }, +})); + +export const Percentage = ({ value }: { value?: string }) => { + const classes = useStyles(); + const theme = useTheme(); + + return ( + + ); +}; diff --git a/plugins/sonarqube/src/components/SonarQubeCard/Rating.tsx b/plugins/sonarqube/src/components/SonarQubeCard/Rating.tsx new file mode 100644 index 0000000000..df90e2b821 --- /dev/null +++ b/plugins/sonarqube/src/components/SonarQubeCard/Rating.tsx @@ -0,0 +1,112 @@ +/* + * 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 { BackstageTheme } from '@backstage/theme'; +import { Avatar } from '@material-ui/core'; +import { lighten, makeStyles } from '@material-ui/core/styles'; +import { CSSProperties } from '@material-ui/styles'; +import React, { useMemo } from 'react'; + +const useStyles = makeStyles((theme: BackstageTheme) => { + const commonCardRating: CSSProperties = { + height: theme.spacing(3), + width: theme.spacing(3), + color: theme.palette.common.white, + }; + + return { + ratingDefault: { + ...commonCardRating, + background: theme.palette.status.aborted, + }, + ratingA: { + ...commonCardRating, + background: theme.palette.status.ok, + }, + ratingB: { + ...commonCardRating, + background: lighten(theme.palette.status.ok, 0.5), + }, + ratingC: { + ...commonCardRating, + background: theme.palette.status.pending, + }, + ratingD: { + ...commonCardRating, + background: theme.palette.status.warning, + }, + ratingE: { + ...commonCardRating, + background: theme.palette.error.main, + }, + }; +}); + +export const Rating = ({ + rating, + hideValue, +}: { + rating?: string; + hideValue?: boolean; +}) => { + const classes = useStyles(); + + const ratingProp = useMemo(() => { + switch (rating) { + case '1.0': + return { + name: 'A', + className: classes.ratingA, + }; + + case '2.0': + return { + name: 'B', + className: classes.ratingB, + }; + + case '3.0': + return { + name: 'C', + className: classes.ratingC, + }; + + case '4.0': + return { + name: 'D', + className: classes.ratingD, + }; + + case '5.0': + return { + name: 'E', + className: classes.ratingE, + }; + + default: + return { + name: '', + className: classes.ratingDefault, + }; + } + }, [classes, rating]); + + return ( + + {!hideValue && ratingProp.name} + + ); +}; diff --git a/plugins/sonarqube/src/components/SonarQubeCard/RatingCard.tsx b/plugins/sonarqube/src/components/SonarQubeCard/RatingCard.tsx new file mode 100644 index 0000000000..cd678b7366 --- /dev/null +++ b/plugins/sonarqube/src/components/SonarQubeCard/RatingCard.tsx @@ -0,0 +1,78 @@ +/* + * 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 { Grid, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React, { ReactNode } from 'react'; + +const useStyles = makeStyles(theme => { + return { + root: { + margin: theme.spacing(1, 0), + }, + upper: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }, + cardTitle: { + textAlign: 'center', + }, + wrapIcon: { + display: 'inline-flex', + verticalAlign: 'baseline', + }, + left: { + display: 'flex', + }, + right: { + display: 'flex', + marginLeft: theme.spacing(0.5), + }, + }; +}); + +export const RatingCard = ({ + leftSlot, + rightSlot, + title, + titleIcon, +}: { + leftSlot: ReactNode; + rightSlot: ReactNode; + title: string; + titleIcon?: ReactNode; +}) => { + const classes = useStyles(); + + return ( + + + + {leftSlot} + + + {rightSlot} + + + + + {titleIcon} {title} + + + + ); +}; diff --git a/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx b/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx new file mode 100644 index 0000000000..8e6cc817a1 --- /dev/null +++ b/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx @@ -0,0 +1,230 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; +import { + EmptyState, + InfoCard, + MissingAnnotationEmptyState, + Progress, + useApi, +} from '@backstage/core'; +import { Chip, Grid } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import BugReport from '@material-ui/icons/BugReport'; +import LockOpen from '@material-ui/icons/LockOpen'; +import SentimentVeryDissatisfied from '@material-ui/icons/SentimentVeryDissatisfied'; +import React, { useMemo } from 'react'; +import { useAsync } from 'react-use'; +import { sonarQubeApiRef } from '../../api'; +import { + SONARQUBE_PROJECT_KEY_ANNOTATION, + useProjectKey, +} from '../useProjectKey'; +import { Percentage } from './Percentage'; +import { Rating } from './Rating'; +import { RatingCard } from './RatingCard'; +import { Value } from './Value'; + +const useStyles = makeStyles(theme => ({ + badgeLabel: { + color: theme.palette.common.white, + }, + badgeError: { + margin: 0, + backgroundColor: theme.palette.error.main, + }, + badgeSuccess: { + margin: 0, + backgroundColor: theme.palette.success.main, + }, + header: { + padding: theme.spacing(2, 2, 2, 2.5), + }, + action: { + margin: 0, + }, + lastAnalyzed: { + color: theme.palette.text.secondary, + }, + disabled: { + backgroundColor: theme.palette.background.default, + }, +})); + +interface DuplicationRating { + greaterThan: number; + rating: '1.0' | '2.0' | '3.0' | '4.0' | '5.0'; +} + +const defaultDuplicationRatings: DuplicationRating[] = [ + { greaterThan: 0, rating: '1.0' }, + { greaterThan: 3, rating: '2.0' }, + { greaterThan: 5, rating: '3.0' }, + { greaterThan: 10, rating: '4.0' }, + { greaterThan: 20, rating: '5.0' }, +]; + +export const SonarQubeCard = ({ + entity, + variant = 'gridItem', + duplicationRatings = defaultDuplicationRatings, +}: { + entity: Entity; + variant?: string; + duplicationRatings?: DuplicationRating[]; +}) => { + const sonarQubeApi = useApi(sonarQubeApiRef); + + const projectTitle = useProjectKey(entity); + + const { value, loading } = useAsync( + async () => sonarQubeApi.getFindingSummary(projectTitle), + [sonarQubeApi, projectTitle], + ); + + const deepLink = + !loading && value + ? { + title: 'View more', + link: value.projectUrl, + } + : undefined; + + const gatePassed = value && value.metrics.alert_status === 'OK'; + + const classes = useStyles(); + + const qualityBadge = !loading && value && ( + + ); + + const duplicationRating = useMemo(() => { + if (loading || !value || !value.metrics.duplicated_lines_density) { + return ''; + } + + let rating = ''; + + for (const r of duplicationRatings) { + if (+value.metrics.duplicated_lines_density >= r.greaterThan) { + rating = r.rating; + } + } + + return rating; + }, [loading, value, duplicationRatings]); + + return ( + + {loading && } + + {!loading && !projectTitle && ( + + )} + + {!loading && projectTitle && !value && ( + + )} + + {!loading && value && ( + <> + + + } + title="Bugs" + leftSlot={} + rightSlot={} + /> + } + title="Vulnerabilities" + leftSlot={} + rightSlot={} + /> + } + title="Code Smells" + leftSlot={} + rightSlot={} + /> +
+ } + rightSlot={} + /> + } + rightSlot={ + + } + /> + + + Last analyzed on{' '} + {new Date(value.lastAnalysis).toLocaleString('en-US', { + timeZone: 'UTC', + day: 'numeric', + month: 'short', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + hour12: false, + })} + + + + )} + + ); +}; diff --git a/plugins/sonarqube/src/components/SonarQubeCard/Value.tsx b/plugins/sonarqube/src/components/SonarQubeCard/Value.tsx new file mode 100644 index 0000000000..e7a5492e68 --- /dev/null +++ b/plugins/sonarqube/src/components/SonarQubeCard/Value.tsx @@ -0,0 +1,34 @@ +/* + * 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 { BackstageTheme } from '@backstage/theme'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; + +const useStyles = makeStyles((theme: BackstageTheme) => { + return { + value: { + fontSize: '1.5rem', + fontWeight: theme.typography.fontWeightMedium, + }, + }; +}); + +export const Value = ({ value }: { value?: string }) => { + const classes = useStyles(); + + return {value}; +}; diff --git a/plugins/sonarqube/src/components/SonarQubeCard/index.ts b/plugins/sonarqube/src/components/SonarQubeCard/index.ts new file mode 100644 index 0000000000..e349f1e1f9 --- /dev/null +++ b/plugins/sonarqube/src/components/SonarQubeCard/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { SonarQubeCard } from './SonarQubeCard'; diff --git a/plugins/sonarqube/src/components/index.ts b/plugins/sonarqube/src/components/index.ts new file mode 100644 index 0000000000..8f0786bb8e --- /dev/null +++ b/plugins/sonarqube/src/components/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export * from './SonarQubeCard'; diff --git a/plugins/sonarqube/src/components/useProjectKey.ts b/plugins/sonarqube/src/components/useProjectKey.ts new file mode 100644 index 0000000000..dcff79d972 --- /dev/null +++ b/plugins/sonarqube/src/components/useProjectKey.ts @@ -0,0 +1,23 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; + +export const SONARQUBE_PROJECT_KEY_ANNOTATION = 'sonarqube.org/project-key'; + +export const useProjectKey = (entity: Entity) => { + return entity?.metadata.annotations?.[SONARQUBE_PROJECT_KEY_ANNOTATION] ?? ''; +}; diff --git a/plugins/sonarqube/src/index.ts b/plugins/sonarqube/src/index.ts new file mode 100644 index 0000000000..f09aeb1038 --- /dev/null +++ b/plugins/sonarqube/src/index.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + +export * from './components'; +export { plugin } from './plugin'; diff --git a/plugins/sonarqube/src/plugin.test.ts b/plugins/sonarqube/src/plugin.test.ts new file mode 100644 index 0000000000..32730b64c3 --- /dev/null +++ b/plugins/sonarqube/src/plugin.test.ts @@ -0,0 +1,23 @@ +/* + * 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 { plugin } from './plugin'; + +describe('sonarqube', () => { + it('should export plugin', () => { + expect(plugin).toBeDefined(); + }); +}); diff --git a/plugins/sonarqube/src/plugin.ts b/plugins/sonarqube/src/plugin.ts new file mode 100644 index 0000000000..154d4ee3b8 --- /dev/null +++ b/plugins/sonarqube/src/plugin.ts @@ -0,0 +1,38 @@ +/* + * 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 { + configApiRef, + createApiFactory, + createPlugin, + discoveryApiRef, +} from '@backstage/core'; +import { SonarQubeApi, sonarQubeApiRef } from './api'; + +export const plugin = createPlugin({ + id: 'sonarqube', + apis: [ + createApiFactory({ + api: sonarQubeApiRef, + deps: { configApi: configApiRef, discoveryApi: discoveryApiRef }, + factory: ({ configApi, discoveryApi }) => + new SonarQubeApi({ + discoveryApi, + baseUrl: configApi.getOptionalString('sonarQube.baseUrl'), + }), + }), + ], +}); diff --git a/plugins/sonarqube/src/setupTests.ts b/plugins/sonarqube/src/setupTests.ts new file mode 100644 index 0000000000..825bcd4115 --- /dev/null +++ b/plugins/sonarqube/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * 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 '@testing-library/jest-dom';