sonarqube improvements
Signed-off-by: manusant <ney.br.santos@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-sonarqube': minor
|
||||
---
|
||||
|
||||
Fix sonarqube annotation parsing. Add content page for Sonarqube.
|
||||
@@ -8,6 +8,7 @@
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { InfoCardVariants } from '@backstage/core-components';
|
||||
import { default as React_2 } from 'react';
|
||||
|
||||
// @public (undocumented)
|
||||
export type DuplicationRating = {
|
||||
@@ -21,15 +22,38 @@ export const EntitySonarQubeCard: (props: {
|
||||
duplicationRatings?: DuplicationRating[] | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntitySonarQubeContentPage: ({
|
||||
title,
|
||||
supportTitle,
|
||||
...otherProps
|
||||
}: SonarQubeContentPageProps) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const isSonarQubeAvailable: (entity: Entity) => boolean;
|
||||
|
||||
// @public (undocumented)
|
||||
export const SONARQUBE_PROJECT_KEY_ANNOTATION = 'sonarqube.org/project-key';
|
||||
|
||||
// @public (undocumented)
|
||||
export const SonarQubeCard: (props: {
|
||||
variant?: InfoCardVariants;
|
||||
duplicationRatings?: DuplicationRating[];
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const SonarQubeContentPage: ({
|
||||
title,
|
||||
supportTitle,
|
||||
...otherProps
|
||||
}: SonarQubeContentPageProps) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type SonarQubeContentPageProps = {
|
||||
title?: string;
|
||||
supportTitle?: string;
|
||||
} & React_2.ComponentPropsWithoutRef<'div'>;
|
||||
|
||||
// @public (undocumented)
|
||||
const sonarQubePlugin: BackstagePlugin<{}, {}, {}>;
|
||||
export { sonarQubePlugin as plugin };
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import {
|
||||
isSonarQubeAvailable,
|
||||
SONARQUBE_PROJECT_KEY_ANNOTATION,
|
||||
} from '../useProjectKey';
|
||||
import { SonarQubeApi, sonarQubeApiRef } from '../../api';
|
||||
|
||||
const Providers = ({
|
||||
annotation,
|
||||
children,
|
||||
}: { annotation: string } & React.PropsWithChildren<any>): JSX.Element => (
|
||||
<TestApiProvider apis={[[sonarQubeApiRef, {} as SonarQubeApi]]}>
|
||||
<EntityProvider
|
||||
entity={{
|
||||
metadata: {
|
||||
name: 'mock',
|
||||
namespace: 'default',
|
||||
annotations: {
|
||||
[annotation]: 'foo/bar',
|
||||
},
|
||||
},
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
}}
|
||||
>
|
||||
<ThemeProvider theme={lightTheme}>{children}</ThemeProvider>
|
||||
</EntityProvider>
|
||||
</TestApiProvider>
|
||||
);
|
||||
|
||||
describe('<SonarQubeContentPage />', () => {
|
||||
beforeAll(() => {
|
||||
jest.mock('@backstage/plugin-sonarqube', () => ({
|
||||
__esModule: true,
|
||||
isSonarQubeAvailable,
|
||||
EntitySonarQubeCard: () => {
|
||||
return <div data-testid="entity-sonarqube-card" />;
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
it('renders EntitySonarQubeCard', async () => {
|
||||
const { SonarQubeContentPage } = require('./SonarQubeContentPage');
|
||||
|
||||
const rendered = await renderInTestApp(
|
||||
<Providers annotation={SONARQUBE_PROJECT_KEY_ANNOTATION}>
|
||||
<SonarQubeContentPage />
|
||||
</Providers>,
|
||||
);
|
||||
expect(rendered.getByText('SonarQube Dashboard')).toBeInTheDocument();
|
||||
expect(rendered.getByText('No information to display')).toBeInTheDocument();
|
||||
expect(
|
||||
rendered.getByText("There is no SonarQube project with key 'bar'."),
|
||||
).toBeInTheDocument();
|
||||
}, 15000);
|
||||
|
||||
it('renders MissingAnnotationEmptyState if sonar annotation is missing', async () => {
|
||||
const { SonarQubeContentPage } = require('./SonarQubeContentPage');
|
||||
|
||||
const rendered = await renderInTestApp(
|
||||
<Providers annotation="foo">
|
||||
<SonarQubeContentPage />
|
||||
</Providers>,
|
||||
);
|
||||
expect(rendered.getByText('Missing Annotation')).toBeInTheDocument();
|
||||
expect(
|
||||
rendered.getAllByText(SONARQUBE_PROJECT_KEY_ANNOTATION, { exact: false })
|
||||
.length,
|
||||
).toBe(2);
|
||||
}, 15000);
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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 {
|
||||
Content,
|
||||
ContentHeader,
|
||||
SupportButton,
|
||||
} from '@backstage/core-components';
|
||||
import { MissingAnnotationEmptyState } from '@backstage/core-components';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import React from 'react';
|
||||
import {
|
||||
isSonarQubeAvailable,
|
||||
SONARQUBE_PROJECT_KEY_ANNOTATION,
|
||||
} from '../useProjectKey';
|
||||
import { SonarQubeCard } from '../SonarQubeCard';
|
||||
|
||||
/** @public */
|
||||
export type SonarQubeContentPageProps = {
|
||||
title?: string;
|
||||
supportTitle?: string;
|
||||
} & React.ComponentPropsWithoutRef<'div'>;
|
||||
|
||||
/** @public */
|
||||
export const SonarQubeContentPage = ({
|
||||
title = 'SonarQube Dashboard',
|
||||
supportTitle,
|
||||
...otherProps
|
||||
}: SonarQubeContentPageProps) => {
|
||||
const { entity } = useEntity();
|
||||
|
||||
return isSonarQubeAvailable(entity) ? (
|
||||
<Content {...otherProps}>
|
||||
<ContentHeader title={title}>
|
||||
{supportTitle && <SupportButton>{supportTitle}</SupportButton>}
|
||||
</ContentHeader>
|
||||
<SonarQubeCard />
|
||||
</Content>
|
||||
) : (
|
||||
<MissingAnnotationEmptyState
|
||||
annotation={SONARQUBE_PROJECT_KEY_ANNOTATION}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
export * from './SonarQubeContentPage';
|
||||
@@ -15,4 +15,8 @@
|
||||
*/
|
||||
|
||||
export * from './SonarQubeCard';
|
||||
export { isSonarQubeAvailable } from './useProjectKey';
|
||||
export * from './SonarQubeContentPage';
|
||||
export {
|
||||
isSonarQubeAvailable,
|
||||
SONARQUBE_PROJECT_KEY_ANNOTATION,
|
||||
} from './useProjectKey';
|
||||
|
||||
@@ -39,10 +39,12 @@ describe('isSonarQubeAvailable', () => {
|
||||
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: '',
|
||||
@@ -59,6 +61,7 @@ describe('isSonarQubeAvailable', () => {
|
||||
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,
|
||||
@@ -68,6 +71,33 @@ describe('useProjectInfo', () => {
|
||||
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);
|
||||
@@ -76,6 +106,7 @@ describe('useProjectInfo', () => {
|
||||
projectKey: DUMMY_KEY,
|
||||
});
|
||||
});
|
||||
|
||||
it('handle empty annotation', () => {
|
||||
const entity = createDummyEntity('');
|
||||
expect(useProjectInfo(entity)).toEqual({
|
||||
@@ -83,6 +114,7 @@ describe('useProjectInfo', () => {
|
||||
projectKey: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('handle non-existent annotation', () => {
|
||||
const entity = {
|
||||
apiVersion: '',
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
/** @public */
|
||||
export const SONARQUBE_PROJECT_KEY_ANNOTATION = 'sonarqube.org/project-key';
|
||||
export const SONARQUBE_PROJECT_INSTANCE_SEPARATOR = '/';
|
||||
|
||||
@@ -42,11 +43,16 @@ export const useProjectInfo = (
|
||||
const annotation =
|
||||
entity?.metadata.annotations?.[SONARQUBE_PROJECT_KEY_ANNOTATION];
|
||||
if (annotation) {
|
||||
if (annotation.indexOf(SONARQUBE_PROJECT_INSTANCE_SEPARATOR) > -1) {
|
||||
[projectInstance, projectKey] = annotation.split(
|
||||
SONARQUBE_PROJECT_INSTANCE_SEPARATOR,
|
||||
2,
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -26,4 +26,5 @@ export {
|
||||
sonarQubePlugin,
|
||||
sonarQubePlugin as plugin,
|
||||
EntitySonarQubeCard,
|
||||
EntitySonarQubeContentPage,
|
||||
} from './plugin';
|
||||
|
||||
@@ -52,3 +52,16 @@ export const EntitySonarQubeCard = sonarQubePlugin.provide(
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export const EntitySonarQubeContentPage = sonarQubePlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'EntitySonarQubeContentPage',
|
||||
component: {
|
||||
lazy: () =>
|
||||
import('./components/SonarQubeContentPage').then(
|
||||
m => m.SonarQubeContentPage,
|
||||
),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user