diff --git a/plugins/sonarqube/dev/index.tsx b/plugins/sonarqube/dev/index.tsx index bba859195f..a34c4d4e82 100644 --- a/plugins/sonarqube/dev/index.tsx +++ b/plugins/sonarqube/dev/index.tsx @@ -19,9 +19,13 @@ import { createDevApp, EntityGridItem } from '@backstage/dev-utils'; import { Grid } from '@material-ui/core'; import React from 'react'; import { EntitySonarQubeCard, sonarQubePlugin } from '../src'; -import { FindingSummary, SonarQubeApi, sonarQubeApiRef } from '../src/api'; -import { SONARQUBE_PROJECT_KEY_ANNOTATION } from '../src/components/useProjectKey'; import { Content, Header, Page } from '@backstage/core-components'; +import { + FindingSummary, + SONARQUBE_PROJECT_KEY_ANNOTATION, + SonarQubeApi, + sonarQubeApiRef, +} from '@backstage/plugin-sonarqube-react'; const entity = (name?: string) => ({ diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index ad6df1db8c..a5465eee78 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -38,6 +38,7 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", + "@backstage/plugin-sonarqube-react": "workspace:^", "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", diff --git a/plugins/sonarqube/src/api/SonarQubeClient.test.ts b/plugins/sonarqube/src/api/SonarQubeClient.test.ts index dc5d7f077e..592f08457f 100644 --- a/plugins/sonarqube/src/api/SonarQubeClient.test.ts +++ b/plugins/sonarqube/src/api/SonarQubeClient.test.ts @@ -17,10 +17,11 @@ import { setupRequestMockHandlers } from '@backstage/test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { FindingSummary, SonarQubeClient } from './index'; +import { SonarQubeClient } from './index'; import { InstanceUrlWrapper, FindingsWrapper } from './types'; import { UrlPatternDiscovery } from '@backstage/core-app-api'; import { IdentityApi } from '@backstage/core-plugin-api'; +import { FindingSummary } from '@backstage/plugin-sonarqube-react'; const server = setupServer(); diff --git a/plugins/sonarqube/src/api/SonarQubeClient.ts b/plugins/sonarqube/src/api/SonarQubeClient.ts index 9ec40e8420..cf893ccef5 100644 --- a/plugins/sonarqube/src/api/SonarQubeClient.ts +++ b/plugins/sonarqube/src/api/SonarQubeClient.ts @@ -15,7 +15,11 @@ */ import fetch from 'cross-fetch'; -import { FindingSummary, Metrics, SonarQubeApi } from './SonarQubeApi'; +import { + FindingSummary, + Metrics, + SonarQubeApi, +} from '@backstage/plugin-sonarqube-react'; import { InstanceUrlWrapper, FindingsWrapper } from './types'; import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; diff --git a/plugins/sonarqube/src/api/index.ts b/plugins/sonarqube/src/api/index.ts index 8c418016cd..ddf355af33 100644 --- a/plugins/sonarqube/src/api/index.ts +++ b/plugins/sonarqube/src/api/index.ts @@ -14,6 +14,4 @@ * limitations under the License. */ -export type { Metrics, FindingSummary, SonarQubeApi } from './SonarQubeApi'; -export { sonarQubeApiRef } from './SonarQubeApi'; export { SonarQubeClient } from './SonarQubeClient'; diff --git a/plugins/sonarqube/src/api/types.ts b/plugins/sonarqube/src/api/types.ts index 348986149d..48c7c41c03 100644 --- a/plugins/sonarqube/src/api/types.ts +++ b/plugins/sonarqube/src/api/types.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { MetricKey } from '@backstage/plugin-sonarqube-react'; + export interface InstanceUrlWrapper { instanceUrl: string; } @@ -23,35 +25,7 @@ export interface FindingsWrapper { 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' - - // security hotspots - | 'security_hotspots_reviewed' - | 'security_review_rating' - - // coverage - | 'coverage' - - // duplicated lines - | 'duplicated_lines_density'; - export interface Measure { metric: MetricKey; value: string; } - -export type SonarUrlProcessorFunc = (identifier: string) => string; diff --git a/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx b/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx index b32a66aaa3..900c8d082c 100644 --- a/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx +++ b/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx @@ -15,6 +15,11 @@ */ import { useEntity } from '@backstage/plugin-catalog-react'; +import { + SONARQUBE_PROJECT_KEY_ANNOTATION, + sonarQubeApiRef, + useProjectInfo, +} from '@backstage/plugin-sonarqube-react'; import { Chip, Grid } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import BugReport from '@material-ui/icons/BugReport'; @@ -23,11 +28,6 @@ import Security from '@material-ui/icons/Security'; import SentimentVeryDissatisfied from '@material-ui/icons/SentimentVeryDissatisfied'; import React, { useMemo } from 'react'; import useAsync from 'react-use/lib/useAsync'; -import { sonarQubeApiRef } from '../../api'; -import { - SONARQUBE_PROJECT_KEY_ANNOTATION, - useProjectInfo, -} from '../useProjectKey'; import { Percentage } from './Percentage'; import { Rating } from './Rating'; import { RatingCard } from './RatingCard'; diff --git a/plugins/sonarqube/src/components/SonarQubeContentPage/SonarQubeContentPage.test.tsx b/plugins/sonarqube/src/components/SonarQubeContentPage/SonarQubeContentPage.test.tsx index 8e847d7c31..22175d14bc 100644 --- a/plugins/sonarqube/src/components/SonarQubeContentPage/SonarQubeContentPage.test.tsx +++ b/plugins/sonarqube/src/components/SonarQubeContentPage/SonarQubeContentPage.test.tsx @@ -22,8 +22,9 @@ import React from 'react'; import { isSonarQubeAvailable, SONARQUBE_PROJECT_KEY_ANNOTATION, -} from '../useProjectKey'; -import { SonarQubeApi, sonarQubeApiRef } from '../../api'; + SonarQubeApi, + sonarQubeApiRef, +} from '@backstage/plugin-sonarqube-react'; const Providers = ({ annotation, diff --git a/plugins/sonarqube/src/components/SonarQubeContentPage/SonarQubeContentPage.tsx b/plugins/sonarqube/src/components/SonarQubeContentPage/SonarQubeContentPage.tsx index 33a145b746..f577d08eeb 100644 --- a/plugins/sonarqube/src/components/SonarQubeContentPage/SonarQubeContentPage.tsx +++ b/plugins/sonarqube/src/components/SonarQubeContentPage/SonarQubeContentPage.tsx @@ -22,11 +22,11 @@ import { import { MissingAnnotationEmptyState } from '@backstage/core-components'; import { useEntity } from '@backstage/plugin-catalog-react'; import React from 'react'; +import { SonarQubeCard } from '../SonarQubeCard'; import { isSonarQubeAvailable, SONARQUBE_PROJECT_KEY_ANNOTATION, -} from '../useProjectKey'; -import { SonarQubeCard } from '../SonarQubeCard'; +} from '@backstage/plugin-sonarqube-react'; /** @public */ export type SonarQubeContentPageProps = { diff --git a/plugins/sonarqube/src/components/index.ts b/plugins/sonarqube/src/components/index.ts index 8e1d93f85b..85ccfe9621 100644 --- a/plugins/sonarqube/src/components/index.ts +++ b/plugins/sonarqube/src/components/index.ts @@ -17,7 +17,3 @@ export { SonarQubeCard } from './SonarQubeCard'; export type { DuplicationRating } from './SonarQubeCard'; export type { SonarQubeContentPageProps } from './SonarQubeContentPage'; -export { - isSonarQubeAvailable, - SONARQUBE_PROJECT_KEY_ANNOTATION, -} from './useProjectKey'; diff --git a/plugins/sonarqube/src/components/useProjectKey.test.ts b/plugins/sonarqube/src/components/useProjectKey.test.ts deleted file mode 100644 index 8686ac82de..0000000000 --- a/plugins/sonarqube/src/components/useProjectKey.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * 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 { - isSonarQubeAvailable, - SONARQUBE_PROJECT_INSTANCE_SEPARATOR, - SONARQUBE_PROJECT_KEY_ANNOTATION, - useProjectInfo, -} from './useProjectKey'; -import { Entity } from '@backstage/catalog-model'; - -const createDummyEntity = (sonarqubeAnnotationValue: string): Entity => { - return { - apiVersion: '', - kind: '', - metadata: { - name: 'dummy', - annotations: { - [SONARQUBE_PROJECT_KEY_ANNOTATION]: sonarqubeAnnotationValue, - }, - }, - }; -}; - -describe('isSonarQubeAvailable', () => { - it('returns true if sonarqube annotation defined', () => { - const entity = createDummyEntity('dummy'); - expect(isSonarQubeAvailable(entity)).toBe(true); - }); - - it('returns false if sonarqube annotation empty', () => { - const entity = createDummyEntity(''); - expect(isSonarQubeAvailable(entity)).toBe(false); - }); - - it('returns false if sonarqube annotation not defined', () => { - const entity = { - apiVersion: '', - kind: '', - metadata: { - name: 'dummy', - annotations: {}, - }, - }; - expect(isSonarQubeAvailable(entity)).toBe(false); - }); -}); - -describe('useProjectInfo', () => { - const DUMMY_INSTANCE = 'dummyInstance'; - const DUMMY_KEY = 'dummyKey'; - - it('parse annotation with key and instance', () => { - const entity = createDummyEntity( - DUMMY_INSTANCE + SONARQUBE_PROJECT_INSTANCE_SEPARATOR + DUMMY_KEY, - ); - expect(useProjectInfo(entity)).toEqual({ - projectInstance: DUMMY_INSTANCE, - projectKey: DUMMY_KEY, - }); - }); - - it('parse annotation with instance, tenant/project-key', () => { - const DUMMY_KEY_WITH_TENANT = 'dummy-tenant/dummyKey'; - const entity = createDummyEntity( - DUMMY_INSTANCE + - SONARQUBE_PROJECT_INSTANCE_SEPARATOR + - DUMMY_KEY_WITH_TENANT, - ); - expect(useProjectInfo(entity)).toEqual({ - projectInstance: DUMMY_INSTANCE, - projectKey: DUMMY_KEY_WITH_TENANT, - }); - }); - - it('parse annotation with instance, tenant:project-key', () => { - const DUMMY_KEY_WITH_TENANT = 'dummy-tenant:dummyKey'; - const entity = createDummyEntity( - DUMMY_INSTANCE + - SONARQUBE_PROJECT_INSTANCE_SEPARATOR + - DUMMY_KEY_WITH_TENANT, - ); - expect(useProjectInfo(entity)).toEqual({ - projectInstance: DUMMY_INSTANCE, - projectKey: DUMMY_KEY_WITH_TENANT, - }); - }); - - // compatibility with previous mono-instance sonarqube config - it('parse annotation with only key', () => { - const entity = createDummyEntity(DUMMY_KEY); - expect(useProjectInfo(entity)).toEqual({ - projectInstance: undefined, - projectKey: DUMMY_KEY, - }); - }); - - it('handle empty annotation', () => { - const entity = createDummyEntity(''); - expect(useProjectInfo(entity)).toEqual({ - projectInstance: undefined, - projectKey: undefined, - }); - }); - - it('handle non-existent annotation', () => { - const entity = { - apiVersion: '', - kind: '', - metadata: { - name: 'dummy', - annotations: {}, - }, - }; - expect(useProjectInfo(entity)).toEqual({ - projectInstance: undefined, - projectKey: undefined, - }); - }); -}); diff --git a/plugins/sonarqube/src/components/useProjectKey.ts b/plugins/sonarqube/src/components/useProjectKey.ts deleted file mode 100644 index 20f7f03bb3..0000000000 --- a/plugins/sonarqube/src/components/useProjectKey.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * 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'; - -/** @public */ -export const SONARQUBE_PROJECT_KEY_ANNOTATION = 'sonarqube.org/project-key'; -export const SONARQUBE_PROJECT_INSTANCE_SEPARATOR = '/'; - -/** @public */ -export const isSonarQubeAvailable = (entity: Entity) => - Boolean(entity.metadata.annotations?.[SONARQUBE_PROJECT_KEY_ANNOTATION]); - -/** - * Try to parse sonarqube information from an entity. - * - * If part or all info are not found, they will default to undefined - * - * @param entity entity to find the sonarqube information from. - * @return a ProjectInfo properly populated. - */ -export const useProjectInfo = ( - entity: Entity, -): { - projectInstance: string | undefined; - projectKey: string | undefined; -} => { - let projectInstance = undefined; - let projectKey = undefined; - const annotation = - entity?.metadata.annotations?.[SONARQUBE_PROJECT_KEY_ANNOTATION]; - if (annotation) { - const instanceSeparatorIndex = annotation.indexOf( - SONARQUBE_PROJECT_INSTANCE_SEPARATOR, - ); - if (instanceSeparatorIndex > -1) { - // Examples: - // instanceA/projectA -> projectInstance = "instanceA" & projectKey = "projectA" - // instanceA/tenantA:projectA -> projectInstance = "instanceA" & projectKey = "tenantA:projectA" - // instanceA/tenantA/projectA -> projectInstance = "instanceA" & projectKey = "tenantA/projectA" - projectInstance = annotation.substring(0, instanceSeparatorIndex); - projectKey = annotation.substring(instanceSeparatorIndex + 1); - } else { - projectKey = annotation; - } - } - return { projectInstance, projectKey }; -}; diff --git a/plugins/sonarqube/src/plugin.ts b/plugins/sonarqube/src/plugin.ts index 5eb5ea82f3..c66d8c4e96 100644 --- a/plugins/sonarqube/src/plugin.ts +++ b/plugins/sonarqube/src/plugin.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { sonarQubeApiRef, SonarQubeClient } from './api'; +import { SonarQubeClient } from './api'; import { createApiFactory, createComponentExtension, @@ -22,6 +22,7 @@ import { discoveryApiRef, identityApiRef, } from '@backstage/core-plugin-api'; +import { sonarQubeApiRef } from '@backstage/plugin-sonarqube-react'; /** @public */ export const sonarQubePlugin = createPlugin({ diff --git a/yarn.lock b/yarn.lock index 2048c5ff5e..bcaf28da01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7785,6 +7785,18 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-sonarqube-react@workspace:^, @backstage/plugin-sonarqube-react@workspace:plugins/sonarqube-react": + version: 0.0.0-use.local + resolution: "@backstage/plugin-sonarqube-react@workspace:plugins/sonarqube-react" + dependencies: + "@backstage/catalog-model": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + peerDependencies: + react: ^16.13.1 || ^17.0.0 + languageName: unknown + linkType: soft + "@backstage/plugin-sonarqube@workspace:plugins/sonarqube": version: 0.0.0-use.local resolution: "@backstage/plugin-sonarqube@workspace:plugins/sonarqube" @@ -7796,6 +7808,7 @@ __metadata: "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" + "@backstage/plugin-sonarqube-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" "@material-ui/core": ^4.12.2