[Plugin] Code Climate

Signed-off-by: Frank Cycan <frank@hqo.co>
This commit is contained in:
Frank Cycan
2022-02-22 11:53:46 -05:00
parent d788fa8495
commit e621d9658b
27 changed files with 1389 additions and 1 deletions
@@ -0,0 +1,26 @@
/*
* 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 { CodeClimateData } from './code-climate-data';
import { createApiRef } from '@backstage/core-plugin-api';
export const codeClimateApiRef = createApiRef<CodeClimateApi>({
id: 'plugin.code-climate.service',
});
export interface CodeClimateApi {
fetchData(repoID: string): Promise<CodeClimateData>;
}
@@ -0,0 +1,184 @@
/*
* 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.
*/
export type CodeClimateRepoData = {
data: {
id: string;
type: string;
attributes: {
analysis_version: number;
badge_token: string;
branch: string;
created_at: string;
delegated_config_repo_id: string;
diff_coverage_enforced: boolean;
diff_coverage_threshold: number;
enable_notifications: boolean;
github_slug: string;
human_name: string;
last_activity_at: string;
test_reporter_id: string | null;
total_coverage_enforced: boolean;
vcs_database_id: string;
vcs_host: string;
score: string | null;
};
relationships: {
latest_default_branch_snapshot: {
data: { id: string; type: string };
};
latest_default_branch_test_report: {
data: { id: string; type: string };
};
account: {
data: { id: string; type: string };
};
};
links: {
self: string;
services: string;
web_coverage: string;
web_issues: string;
maintainability_badge: string;
test_coverage_badge: string;
};
meta: { permissions: { admin: boolean } };
};
};
export type CodeClimateMaintainabilityData = {
data: {
id: string;
type: string;
attributes: {
commit_sha: string;
committed_at: string;
created_at: string;
lines_of_code: number;
ratings: [
{
path: string;
letter: string;
measure: {
value: number;
unit: string;
meta: {
remediation_time: { value: number; unit: string };
implementation_time: {
value: number;
unit: string;
};
};
};
pillar: string;
},
];
gpa: string | null;
worker_version: number;
};
meta: {
issues_count: number;
measures: {
remediation: { value: number; unit: string };
technical_debt_ratio: {
value: number;
unit: string;
meta: {
remediation_time: { value: number; unit: string };
implementation_time: {
value: number;
unit: string;
};
};
};
};
};
};
};
export type CodeClimateTestCoverageData = {
data: {
id: string;
type: string;
attributes: {
branch: string;
commit_sha: string;
committed_at: string;
covered_percent: number;
lines_of_code: number;
rating: {
path: string;
letter: string;
measure: { value: number; unit: string };
pillar: string;
};
received_at: string;
state: string;
};
};
};
export type CodeClimateIssuesData = {
data: [
{
id: string;
type: string;
attributes: {
categories: string[];
check_name: string;
constant_name: string;
content: { body: string };
description: string;
engine_name: string;
fingerprint: string;
location: {
path: string;
end_line: number;
start_line: number;
};
other_locations: string[];
remediation_points: number;
severity: string;
};
meta: { permissions: { manageable: boolean } };
},
];
links: {
self: string;
next: string;
last: string;
};
meta: { current_page: number; total_pages: number; total_count: number };
};
export type CodeClimateData = {
repoID: string;
maintainability: {
letter: string;
value: string;
};
testCoverage: {
letter: string;
value: string;
};
numberOfCodeSmells: number;
numberOfDuplication: number;
numberOfOtherIssues: number;
};
export type CodeClimateApiError = {
detail: string;
};
+21
View File
@@ -0,0 +1,21 @@
/*
* 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.
*/
export * from './mock';
export type { CodeClimateApi } from './code-climate-api';
export { codeClimateApiRef } from './code-climate-api';
export type { CodeClimateData } from './code-climate-data';
export { ProductionCodeClimateApi } from './production-api';
@@ -0,0 +1,49 @@
{
"data": {
"id": "6b8cc37a64b741dd9d516119",
"type": "snapshots",
"attributes": {
"commit_sha": "d670460b4b4aece5915caf5c68d12f560a9fe3e4",
"committed_at": "2022-01-14T15:17:29.311Z",
"created_at": "2022-01-31T10:07:40.415Z",
"lines_of_code": 8854,
"ratings": [
{
"path": "/",
"letter": "B",
"measure": {
"value": 5.264879308188034,
"unit": "percent",
"meta": {
"remediation_time": { "value": 7186.52, "unit": "minute" },
"implementation_time": {
"value": 136499.2353922225,
"unit": "minute"
}
}
},
"pillar": "Maintainability"
}
],
"gpa": null,
"worker_version": 69440
},
"meta": {
"issues_count": 127,
"measures": {
"remediation": { "value": 7186.52, "unit": "minute" },
"technical_debt_ratio": {
"value": 5.264879308188034,
"unit": "percent",
"meta": {
"remediation_time": { "value": 7186.52, "unit": "minute" },
"implementation_time": {
"value": 136499.2353922225,
"unit": "minute"
}
}
}
}
}
}
}
@@ -0,0 +1,21 @@
{
"data": {
"id": "6b8cc37a64b741dd9d516119",
"type": "test_reports",
"attributes": {
"branch": "master",
"commit_sha": "d670460b4b4aece5915caf5c68d12f560a9fe3e4",
"committed_at": "2022-01-14T15:17:26.000Z",
"covered_percent": 88.41059602649007,
"lines_of_code": 8854,
"rating": {
"path": "/",
"letter": "B",
"measure": { "value": 88.41059602649007, "unit": "percent" },
"pillar": "Test Coverage"
},
"received_at": "2022-01-14T15:25:38.352Z",
"state": "done"
}
}
}
@@ -0,0 +1,18 @@
/*
* 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.
*/
export { mockData } from './mock-api';
export { MockCodeClimateApi } from './mock-api';
@@ -0,0 +1,56 @@
/*
* 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 { CodeClimateData } from '../code-climate-data';
import { CodeClimateApi } from '../code-climate-api';
import maintainabilityMock from './code-climate-maintainability-mock.json';
import testCoverageMock from './code-climate-test-coverage-mock.json';
import { Duration } from 'luxon';
import humanizeDuration from 'humanize-duration';
const maintainabilityData = maintainabilityMock.data.attributes.ratings[0];
const testCoverageData = testCoverageMock.data.attributes.rating;
const maintainabilityValue: any = {};
maintainabilityValue[
maintainabilityData.measure.meta.implementation_time.unit
] = maintainabilityData.measure.meta.implementation_time.value.toFixed();
export const mockData: CodeClimateData = {
repoID: '6b8cc37a64b741dd9d516119',
maintainability: {
letter: maintainabilityData.letter,
value: humanizeDuration(
Duration.fromObject(maintainabilityValue).toMillis(),
{ largest: 1 },
),
},
testCoverage: {
letter: testCoverageData.letter,
value: testCoverageData.measure.value.toFixed(),
},
numberOfCodeSmells: 97,
numberOfDuplication: 49,
numberOfOtherIssues: 26,
};
export class MockCodeClimateApi implements CodeClimateApi {
fetchData(): Promise<CodeClimateData> {
return new Promise(resolve => {
setTimeout(() => resolve(mockData), 800);
});
}
}
@@ -0,0 +1,160 @@
/*
* 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 {
CodeClimateData,
CodeClimateRepoData,
CodeClimateMaintainabilityData,
CodeClimateTestCoverageData,
CodeClimateIssuesData,
} from './code-climate-data';
import { CodeClimateApi } from './code-climate-api';
import { DiscoveryApi } from '@backstage/core-plugin-api';
import { Duration } from 'luxon';
import humanizeDuration from 'humanize-duration';
const pageQuery = encodeURIComponent('page[size]');
const statusFilter = encodeURIComponent('filter[status][$in][]');
const categoriesFilter = encodeURIComponent('filter[categories][$in][]');
const basicIssuesOptions = `${pageQuery}=1&${statusFilter}=open&${statusFilter}=confirmed`;
const codeSmellsQuery = `${basicIssuesOptions}&${categoriesFilter}=Complexity`;
const duplicationQuery = `${basicIssuesOptions}&${categoriesFilter}=Duplication`;
const otherIssuesQuery = `${basicIssuesOptions}&${categoriesFilter}=Bug%20Risk`;
export class ProductionCodeClimateApi implements CodeClimateApi {
constructor(private readonly discoveryApi: DiscoveryApi) {}
async fetchAllData(options: {
apiUrl: string;
repoID: string;
snapshotID: string;
testReportID: string;
}): Promise<any> {
const { apiUrl, repoID, snapshotID, testReportID } = options;
const [
maintainabilityResponse,
testCoverageResponse,
codeSmellsResponse,
duplicationResponse,
otherIssuesResponse,
] = await Promise.all([
await fetch(`${apiUrl}/repos/${repoID}/snapshots/${snapshotID}`),
await fetch(`${apiUrl}/repos/${repoID}/test_reports/${testReportID}`),
await fetch(
`${apiUrl}/repos/${repoID}/snapshots/${snapshotID}/issues?${codeSmellsQuery}`,
),
await fetch(
`${apiUrl}/repos/${repoID}/snapshots/${snapshotID}/issues?${duplicationQuery}`,
),
await fetch(
`${apiUrl}/repos/${repoID}/snapshots/${snapshotID}/issues?${otherIssuesQuery}`,
),
]);
if (
!maintainabilityResponse.ok ||
!testCoverageResponse.ok ||
!codeSmellsResponse.ok ||
!duplicationResponse.ok ||
!otherIssuesResponse.ok
) {
throw new Error('Failed fetching Code Climate info');
}
const maintainabilityData = (
(await maintainabilityResponse.json()) as CodeClimateMaintainabilityData
).data.attributes.ratings[0];
const testCoverageData = (
(await testCoverageResponse.json()) as CodeClimateTestCoverageData
).data.attributes.rating;
const codeSmellsData = (
(await codeSmellsResponse.json()) as CodeClimateIssuesData
).meta.total_count;
const duplicationData = (
(await duplicationResponse.json()) as CodeClimateIssuesData
).meta.total_count;
const otherIssuesData = (
(await otherIssuesResponse.json()) as CodeClimateIssuesData
).meta.total_count;
return [
maintainabilityData,
testCoverageData,
codeSmellsData,
duplicationData,
otherIssuesData,
];
}
async fetchData(repoID: string): Promise<CodeClimateData> {
if (!repoID) {
throw new Error('No Repo id found');
}
const apiUrl = `${await this.discoveryApi.getBaseUrl(
'proxy',
)}/codeclimate/api`;
const repoResponse = await fetch(`${apiUrl}/repos/${repoID}`);
if (!repoResponse.ok) {
throw new Error('Failed fetching Code Climate info');
}
const repoData = ((await repoResponse.json()) as CodeClimateRepoData).data;
const snapshotID =
repoData.relationships.latest_default_branch_snapshot.data.id;
const testReportID =
repoData.relationships.latest_default_branch_test_report.data.id;
const [
maintainabilityData,
testCoverageData,
codeSmellsData,
duplicationData,
otherIssuesData,
] = await this.fetchAllData({
apiUrl,
repoID,
snapshotID,
testReportID,
});
const maintainabilityValue: any = {};
maintainabilityValue[
maintainabilityData.measure.meta.implementation_time.unit
] = maintainabilityData.measure.meta.implementation_time.value.toFixed();
return {
repoID,
maintainability: {
letter: maintainabilityData.letter,
value: humanizeDuration(
Duration.fromObject(maintainabilityValue).toMillis(),
{ largest: 1 },
),
},
testCoverage: {
letter: testCoverageData.letter,
value: testCoverageData.measure.value.toFixed(),
},
numberOfCodeSmells: codeSmellsData,
numberOfDuplication: duplicationData,
numberOfOtherIssues: otherIssuesData,
};
}
}
@@ -0,0 +1,25 @@
/*
* 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 React from 'react';
import { InfoCard } from '@backstage/core-components';
import { CodeClimateCardContents } from '../CodeClimateCardContents';
export const CodeClimateCard = () => (
<InfoCard title="Code Climate Summary">
<CodeClimateCardContents />
</InfoCard>
);
@@ -0,0 +1,17 @@
/*
* 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.
*/
export { CodeClimateCard } from './CodeClimateCard';
@@ -0,0 +1,64 @@
/*
* 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 React from 'react';
import useAsync from 'react-use/lib/useAsync';
import { codeClimateApiRef } from '../../api';
import { CodeClimateTable } from '../CodeClimateTable';
import { CODECLIMATE_REPO_ID_ANNOTATION } from '../../plugin';
import { useEntity } from '@backstage/plugin-catalog-react';
import {
EmptyState,
ErrorPanel,
MissingAnnotationEmptyState,
Progress,
} from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';
export const CodeClimateCardContents = () => {
const { entity } = useEntity();
const codeClimateApi = useApi(codeClimateApiRef);
const repoID =
entity?.metadata.annotations?.[CODECLIMATE_REPO_ID_ANNOTATION] ?? '';
const { loading, value, error } = useAsync(
() => codeClimateApi.fetchData(repoID),
[codeClimateApi, repoID],
);
if (loading) {
return <Progress />;
} else if (!repoID) {
return (
<MissingAnnotationEmptyState
annotation={CODECLIMATE_REPO_ID_ANNOTATION}
/>
);
} else if (error) {
return <ErrorPanel error={error} />;
} else if (!value) {
return (
<EmptyState
missing="info"
title="No information to display"
description={`There is no Code Climate repo setup with id '${repoID}'.`}
/>
);
}
return <CodeClimateTable codeClimateData={value} />;
};
@@ -0,0 +1,17 @@
/*
* 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.
*/
export { CodeClimateCardContents } from './CodeClimateCardContents';
@@ -0,0 +1,37 @@
/*
* 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 React from 'react';
import { render } from '@testing-library/react';
import { CodeClimateTable } from './CodeClimateTable';
import { mockData } from '../../api/mock';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
describe('CodeClimateTable', () => {
it('should render values in a table', async () => {
const table = await render(
<ThemeProvider theme={lightTheme}>
<CodeClimateTable codeClimateData={mockData} />
</ThemeProvider>,
);
expect(await table.findByText('3 months')).toBeInTheDocument();
expect(await table.findByText('88%')).toBeInTheDocument();
expect(await table.findByText('97')).toBeInTheDocument();
expect(await table.findByText('49')).toBeInTheDocument();
expect(await table.findByText('26')).toBeInTheDocument();
});
});
@@ -0,0 +1,212 @@
/*
* 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 React from 'react';
import { CodeClimateData } from '../../api';
import { Link } from '@backstage/core-components';
import { Box, makeStyles, Typography } from '@material-ui/core';
import { BackstageTheme } from '@backstage/theme';
const letterStyle = {
color: 'white',
border: 0,
borderRadius: '3px',
fontSize: '40px',
padding: '5px 20px',
};
const fontSize = {
fontSize: '25px',
};
const letterColor = (letter: string) => {
if (letter === 'A') {
return '#45d298';
} else if (letter === 'B') {
return '#a5d86e';
} else if (letter === 'C') {
return '#f1ce0c';
} else if (letter === 'D') {
return '#f29141';
} else if (letter === 'F') {
return '#df5869';
}
return '#45d298';
};
const useStyles = makeStyles<
BackstageTheme,
{
maintainabilityLetter: string;
testCoverageLetter: string;
}
>({
spaceAround: {
display: 'flex',
justifyContent: 'space-around',
},
spaceBetween: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
},
maintainabilityLetterColor: {
...letterStyle,
backgroundColor: props => letterColor(props.maintainabilityLetter),
},
testCoverageLetterColor: {
...letterStyle,
backgroundColor: props => letterColor(props.testCoverageLetter),
},
fontSize: {
...fontSize,
},
letterDetails: {
...fontSize,
paddingLeft: '10px',
},
paddingSides20: {
padding: '0px 20px',
},
});
export const CodeClimateTable = ({
codeClimateData,
}: {
codeClimateData: CodeClimateData;
}) => {
const {
repoID,
maintainability: {
letter: maintainabilityLetter,
value: maintainabilityValue,
},
testCoverage: { letter: testCoverageLetter, value: testCoverageValue },
numberOfCodeSmells,
numberOfDuplication,
numberOfOtherIssues,
} = codeClimateData;
const classes = useStyles({ maintainabilityLetter, testCoverageLetter });
if (!codeClimateData) {
return null;
}
return (
<>
<div className={classes.spaceAround}>
<div>
<Typography variant="h6" component="p">
Maintainability
</Typography>
<div className={classes.spaceBetween}>
<Typography
className={classes.maintainabilityLetterColor}
variant="body2"
component="p"
>
{maintainabilityLetter}
</Typography>
<Link to={`https://codeclimate.com/repos/${repoID}`}>
<Typography
className={classes.letterDetails}
variant="body2"
component="p"
>
{maintainabilityValue}
</Typography>
</Link>
</div>
</div>
<div>
<Typography variant="h6" component="p">
Test Coverage
</Typography>
<div className={classes.spaceBetween}>
<Typography
className={classes.testCoverageLetterColor}
variant="body2"
component="p"
>
{testCoverageLetter}
</Typography>
<Link to={`https://codeclimate.com/repos/${repoID}`}>
<Typography
className={classes.letterDetails}
variant="body2"
component="p"
>
{testCoverageValue}%
</Typography>
</Link>
</div>
</div>
</div>
<Box className={classes.spaceAround} paddingTop="30px">
<div>
<Typography variant="h6" component="p">
Code Smells:
</Typography>
<Link
to={`https://codeclimate.com/repos/${repoID}/issues?category%5B%5D=complexity&status%5B%5D=&status%5B%5D=open&status%5B%5D=confirmed`}
>
<Typography
className={classes.fontSize}
variant="body2"
component="p"
>
{numberOfCodeSmells}
</Typography>
</Link>
</div>
<Box paddingLeft="20" paddingRight="20">
<Typography variant="h6" component="p">
Duplication:
</Typography>
<Link
to={`https://codeclimate.com/repos/${repoID}/issues?category%5B%5D=duplication&status%5B%5D=&status%5B%5D=open&status%5B%5D=confirmed`}
>
<Typography
className={classes.fontSize}
variant="body2"
component="p"
>
{numberOfDuplication}
</Typography>
</Link>
</Box>
<div>
<Typography variant="h6" component="p">
Other Issues:
</Typography>
<Link
to={`https://codeclimate.com/repos/${repoID}/issues?category%5B%5D=bugrisk&status%5B%5D=&status%5B%5D=open&status%5B%5D=confirmed`}
>
<Typography
className={classes.fontSize}
variant="body2"
component="p"
>
{numberOfOtherIssues}
</Typography>
</Link>
</div>
</Box>
</>
);
};
@@ -0,0 +1,17 @@
/*
* 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.
*/
export { CodeClimateTable } from './CodeClimateTable';
+24
View File
@@ -0,0 +1,24 @@
/*
* 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.
*/
/**
* A Backstage plugin that integrates towards Code Climate
*
* @packageDocumentation
*/
export * from './api';
export { codeClimatePlugin, EntityCodeClimateCard } from './plugin';
+23
View File
@@ -0,0 +1,23 @@
/*
* 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 { codeClimatePlugin } from './plugin';
describe('codeclimate', () => {
it('should export plugin', () => {
expect(codeClimatePlugin).toBeDefined();
});
});
+58
View File
@@ -0,0 +1,58 @@
/*
* 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 { ProductionCodeClimateApi, codeClimateApiRef } from './api';
import {
createApiFactory,
createPlugin,
createRouteRef,
discoveryApiRef,
identityApiRef,
createComponentExtension,
} from '@backstage/core-plugin-api';
export const CODECLIMATE_REPO_ID_ANNOTATION = 'codeclimate.com/repo-id';
export const rootRouteRef = createRouteRef({
id: 'code-climate',
});
export const codeClimatePlugin = createPlugin({
id: 'code-climate',
apis: [
createApiFactory({
api: codeClimateApiRef,
deps: {
discoveryApi: discoveryApiRef,
identityApi: identityApiRef,
},
factory: ({ discoveryApi }) => new ProductionCodeClimateApi(discoveryApi),
}),
],
routes: {
root: rootRouteRef,
},
});
export const EntityCodeClimateCard = codeClimatePlugin.provide(
createComponentExtension({
name: 'EntityCodeClimateCard',
component: {
lazy: () =>
import('./components/CodeClimateCard').then(m => m.CodeClimateCard),
},
}),
);
+20
View File
@@ -0,0 +1,20 @@
/*
* 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.
*/
/* eslint-disable import/no-extraneous-dependencies */
import '@testing-library/jest-dom';
import 'cross-fetch/polyfill';