feat(plugin-sonarqube): add links to each rating
This commit is contained in:
@@ -114,22 +114,29 @@ describe('SonarQubeApi', () => {
|
||||
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);
|
||||
expect(summary).toEqual(
|
||||
expect.objectContaining({
|
||||
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',
|
||||
}),
|
||||
);
|
||||
expect(summary?.getIssuesUrl('CODE_SMELL')).toEqual(
|
||||
'https://sonarcloud.io/project/issues?id=our-service&types=CODE_SMELL&resolved=false',
|
||||
);
|
||||
expect(summary?.getComponentMeasuresUrl('COVERAGE')).toEqual(
|
||||
'https://sonarcloud.io/component_measures?id=our-service&metric=coverage&resolved=false&view=list',
|
||||
);
|
||||
});
|
||||
|
||||
it('should report finding summary (custom baseUrl)', async () => {
|
||||
@@ -142,20 +149,28 @@ describe('SonarQubeApi', () => {
|
||||
|
||||
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);
|
||||
expect(summary).toEqual(
|
||||
expect.objectContaining({
|
||||
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,
|
||||
);
|
||||
expect(summary?.getIssuesUrl('CODE_SMELL')).toEqual(
|
||||
'http://a.instance.local/project/issues?id=our-service&types=CODE_SMELL&resolved=false',
|
||||
);
|
||||
expect(summary?.getComponentMeasuresUrl('COVERAGE')).toEqual(
|
||||
'http://a.instance.local/component_measures?id=our-service&metric=coverage&resolved=false&view=list',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,12 @@
|
||||
|
||||
import { createApiRef, DiscoveryApi } from '@backstage/core';
|
||||
import fetch from 'cross-fetch';
|
||||
import { ComponentWrapper, MeasuresWrapper, MetricKey } from './types';
|
||||
import {
|
||||
ComponentWrapper,
|
||||
MeasuresWrapper,
|
||||
MetricKey,
|
||||
SonarUrlProcessorFunc,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* Define a type to make sure that all metrics are used
|
||||
@@ -29,6 +34,8 @@ export interface FindingSummary {
|
||||
lastAnalysis: string;
|
||||
metrics: Metrics;
|
||||
projectUrl: string;
|
||||
getIssuesUrl: SonarUrlProcessorFunc;
|
||||
getComponentMeasuresUrl: SonarUrlProcessorFunc;
|
||||
}
|
||||
|
||||
export const sonarQubeApiRef = createApiRef<SonarQubeApi>({
|
||||
@@ -105,6 +112,14 @@ export class SonarQubeApi {
|
||||
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`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,3 +53,5 @@ export interface Measure {
|
||||
value: string;
|
||||
component: string;
|
||||
}
|
||||
|
||||
export type SonarUrlProcessorFunc = (identifier: string) => string;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Grid, Typography } from '@material-ui/core';
|
||||
import { Grid, Typography, Link } from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
@@ -50,29 +50,33 @@ export const RatingCard = ({
|
||||
rightSlot,
|
||||
title,
|
||||
titleIcon,
|
||||
link,
|
||||
}: {
|
||||
leftSlot: ReactNode;
|
||||
rightSlot: ReactNode;
|
||||
title: string;
|
||||
titleIcon?: ReactNode;
|
||||
link: string;
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Grid item className={classes.root}>
|
||||
<Grid item className={classes.upper}>
|
||||
<Grid item className={classes.left}>
|
||||
{leftSlot}
|
||||
<Link href={link} color="inherit" underline="none">
|
||||
<Grid item className={classes.root}>
|
||||
<Grid item className={classes.upper}>
|
||||
<Grid item className={classes.left}>
|
||||
{leftSlot}
|
||||
</Grid>
|
||||
<Grid item className={classes.right}>
|
||||
{rightSlot}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item className={classes.right}>
|
||||
{rightSlot}
|
||||
<Grid item className={classes.cardTitle}>
|
||||
<Typography variant="body1" className={classes.wrapIcon}>
|
||||
{titleIcon} {title}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item className={classes.cardTitle}>
|
||||
<Typography variant="body1" className={classes.wrapIcon}>
|
||||
{titleIcon} {title}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -181,29 +181,34 @@ export const SonarQubeCard = ({
|
||||
<RatingCard
|
||||
titleIcon={<BugReport />}
|
||||
title="Bugs"
|
||||
link={value.getIssuesUrl('BUG')}
|
||||
leftSlot={<Value value={value.metrics.bugs} />}
|
||||
rightSlot={<Rating rating={value.metrics.reliability_rating} />}
|
||||
/>
|
||||
<RatingCard
|
||||
titleIcon={<LockOpen />}
|
||||
title="Vulnerabilities"
|
||||
link={value.getIssuesUrl('VULNERABILITY')}
|
||||
leftSlot={<Value value={value.metrics.vulnerabilities} />}
|
||||
rightSlot={<Rating rating={value.metrics.security_rating} />}
|
||||
/>
|
||||
<RatingCard
|
||||
titleIcon={<SentimentVeryDissatisfied />}
|
||||
title="Code Smells"
|
||||
link={value.getIssuesUrl('CODE_SMELL')}
|
||||
leftSlot={<Value value={value.metrics.code_smells} />}
|
||||
rightSlot={<Rating rating={value.metrics.sqale_rating} />}
|
||||
/>
|
||||
<div style={{ width: '100%' }} />
|
||||
<RatingCard
|
||||
link={value.getComponentMeasuresUrl('COVERAGE')}
|
||||
title="Coverage"
|
||||
leftSlot={<Percentage value={value.metrics.coverage} />}
|
||||
rightSlot={<Value value={`${value.metrics.coverage}%`} />}
|
||||
/>
|
||||
<RatingCard
|
||||
title="Duplications"
|
||||
link={value.getComponentMeasuresUrl('DUPLICATED_LINES_DENSITY')}
|
||||
leftSlot={<Rating rating={duplicationRating} hideValue />}
|
||||
rightSlot={
|
||||
<Value value={`${value.metrics.duplicated_lines_density}%`} />
|
||||
|
||||
Reference in New Issue
Block a user