@@ -1,19 +1,23 @@
|
||||
# code-coverage
|
||||
|
||||
This is the frontend part of the code-coverage plugin. It takes care of processing various coverage formats and standardizing them into a single json format, used by the frontend.
|
||||
This is the frontend part of the code-coverage plugin. It displays code coverage summaries for your entities.
|
||||
|
||||
## Configuring your entity
|
||||
|
||||
In order to use this plugin, you must set the `backstage.io/code-coverage` annotation.
|
||||
In order to use this plugin, you must set the `backstage.io/code-coverage` annotation on entities for which coverage ingestion has been enabled.
|
||||
|
||||
```yaml
|
||||
backstage.io/code-coverage: enabled
|
||||
metadata:
|
||||
annotations:
|
||||
backstage.io/code-coverage: enabled
|
||||
```
|
||||
|
||||
There's a feature to only include files that are in VCS in the coverage report, this is helpful to not count generated files for example. To enable this set the `backstage.io/code-coverage` annotation to `scm-only`.
|
||||
|
||||
```yaml
|
||||
backstage.io/code-coverage: scm-only
|
||||
metadata:
|
||||
annotations:
|
||||
backstage.io/code-coverage: scm-only
|
||||
```
|
||||
|
||||
Note: It may be required to set the [`backstage.io/source-location` annotation](https://backstage.io/docs/features/software-catalog/well-known-annotations#backstageiosource-location), however this should generally not be needed.
|
||||
|
||||
@@ -20,13 +20,11 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.2",
|
||||
"@backstage/config": "^0.1.3",
|
||||
"@backstage/catalog-model": "^0.7.7",
|
||||
"@backstage/config": "^0.1.4",
|
||||
"@backstage/core": "^0.7.5",
|
||||
"@backstage/core-api": "^0.2.12",
|
||||
"@backstage/errors": "^0.1.0",
|
||||
"@backstage/plugin-catalog-react": "^0.1.0",
|
||||
"@backstage/plugin-code-coverage-backend": "^0.1.0",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/plugin-catalog-react": "^0.1.4",
|
||||
"@backstage/theme": "^0.2.5",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -14,27 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createApiRef, DiscoveryApi } from '@backstage/core';
|
||||
import { EntityName, stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import {
|
||||
JsonCodeCoverage,
|
||||
JsonCoverageHistory,
|
||||
} from '@backstage/plugin-code-coverage-backend';
|
||||
import { createApiRef, DiscoveryApi } from '@backstage/core';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
|
||||
export class FetchError extends Error {
|
||||
get name(): string {
|
||||
return this.constructor.name;
|
||||
}
|
||||
|
||||
static async forResponse(resp: Response): Promise<FetchError> {
|
||||
return new FetchError(
|
||||
`Request failed with status code ${
|
||||
resp.status
|
||||
}.\nReason: ${await resp.text()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
import { JsonCodeCoverage, JsonCoverageHistory } from './types';
|
||||
|
||||
export type CodeCoverageApi = {
|
||||
discovery: DiscoveryApi;
|
||||
@@ -79,7 +62,7 @@ export class CodeCoverageRestApi implements CodeCoverageApi {
|
||||
async getCoverageForEntity(
|
||||
entityName: EntityName,
|
||||
): Promise<JsonCodeCoverage> {
|
||||
const entity = encodeURI(stringifyEntityRef(entityName));
|
||||
const entity = encodeURIComponent(stringifyEntityRef(entityName));
|
||||
return (await this.fetch<JsonCodeCoverage>(
|
||||
`/report?entity=${entity}`,
|
||||
)) as JsonCodeCoverage;
|
||||
@@ -89,7 +72,7 @@ export class CodeCoverageRestApi implements CodeCoverageApi {
|
||||
entityName: EntityName,
|
||||
filePath: string,
|
||||
): Promise<string> {
|
||||
const entity = encodeURI(stringifyEntityRef(entityName));
|
||||
const entity = encodeURIComponent(stringifyEntityRef(entityName));
|
||||
return await this.fetch<string>(
|
||||
`/file-content?entity=${entity}&path=${encodeURI(filePath)}`,
|
||||
);
|
||||
@@ -99,11 +82,11 @@ export class CodeCoverageRestApi implements CodeCoverageApi {
|
||||
entityName: EntityName,
|
||||
limit?: number,
|
||||
): Promise<JsonCoverageHistory> {
|
||||
const entity = encodeURI(stringifyEntityRef(entityName));
|
||||
const entity = encodeURIComponent(stringifyEntityRef(entityName));
|
||||
const hasValidLimit = limit && limit > 0;
|
||||
return (await this.fetch<JsonCoverageHistory>(
|
||||
`/history?entity=${entity}${
|
||||
hasValidLimit ? `&limit=${encodeURI(`${limit}`)}` : ''
|
||||
hasValidLimit ? `&limit=${encodeURIComponent(String(limit))}` : ''
|
||||
}`,
|
||||
)) as JsonCoverageHistory;
|
||||
}
|
||||
|
||||
+15
-16
@@ -14,7 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Progress, ResponseErrorPanel, useApi } from '@backstage/core';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
@@ -23,27 +25,24 @@ import {
|
||||
makeStyles,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import TrendingDownIcon from '@material-ui/icons/TrendingDown';
|
||||
import TrendingFlatIcon from '@material-ui/icons/TrendingFlat';
|
||||
import TrendingUpIcon from '@material-ui/icons/TrendingUp';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import { ClassNameMap } from '@material-ui/styles';
|
||||
import React from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import {
|
||||
LineChart,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Tooltip,
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
Line,
|
||||
CartesianGrid,
|
||||
LineChart,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from 'recharts';
|
||||
import { useAsync } from 'react-use';
|
||||
import { useApi } from '@backstage/core-api';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { codeCoverageApiRef } from '../../api';
|
||||
import { Progress, ResponseErrorPanel } from '@backstage/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import TrendingDownIcon from '@material-ui/icons/TrendingDown';
|
||||
import TrendingUpIcon from '@material-ui/icons/TrendingUp';
|
||||
import TrendingFlatIcon from '@material-ui/icons/TrendingFlat';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { ClassNameMap } from '@material-ui/styles';
|
||||
|
||||
type Coverage = 'line' | 'branch';
|
||||
|
||||
|
||||
+3
-5
@@ -13,9 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
// styles
|
||||
import React from 'react';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
@@ -64,7 +63,8 @@ type CodeRowProps = {
|
||||
lineContent: string;
|
||||
lineHits?: number | null;
|
||||
};
|
||||
const CodeRow = ({
|
||||
|
||||
export const CodeRow = ({
|
||||
lineNumber,
|
||||
lineContent,
|
||||
lineHits = null,
|
||||
@@ -98,5 +98,3 @@ const CodeRow = ({
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default CodeRow;
|
||||
@@ -14,17 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useApi } from '@backstage/core-api';
|
||||
import { Progress, ResponseErrorPanel, useApi } from '@backstage/core';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { makeStyles, Paper } from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import React from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { codeCoverageApiRef } from '../../api';
|
||||
import { Progress, ResponseErrorPanel } from '@backstage/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import { makeStyles, Paper } from '@material-ui/core';
|
||||
import { FileEntry } from '../../types';
|
||||
import { CodeRow } from './CodeRow';
|
||||
import { highlightLines } from './Highlighter';
|
||||
import CoverageRow from './CoverageRow';
|
||||
import { FileEntry } from '@backstage/plugin-code-coverage-backend';
|
||||
|
||||
type Props = {
|
||||
filename: string;
|
||||
@@ -61,7 +60,7 @@ const FormattedLines = ({
|
||||
{highlightedLines.map((lineContent, idx) => {
|
||||
const line = idx + 1;
|
||||
return (
|
||||
<CoverageRow
|
||||
<CodeRow
|
||||
key={line}
|
||||
lineNumber={line}
|
||||
lineContent={lineContent}
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useApi } from '@backstage/core-api';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
Progress,
|
||||
ResponseErrorPanel,
|
||||
Table,
|
||||
TableColumn,
|
||||
useApi,
|
||||
} from '@backstage/core';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
@@ -30,13 +30,13 @@ import {
|
||||
Modal,
|
||||
Tooltip,
|
||||
} from '@material-ui/core';
|
||||
import DescriptionIcon from '@material-ui/icons/Description';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { codeCoverageApiRef } from '../../api';
|
||||
import DescriptionIcon from '@material-ui/icons/Description';
|
||||
import { FileEntry } from '../../types';
|
||||
import { FileContent } from './FileContent';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import { FileEntry } from '@backstage/plugin-code-coverage-backend';
|
||||
|
||||
type FileStructureObject = Record<string, any>;
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2021 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 { EntityName } from '@backstage/catalog-model';
|
||||
|
||||
export type JsonCodeCoverage = {
|
||||
metadata: CoverageMetadata;
|
||||
entity: EntityName;
|
||||
files: Array<FileEntry>;
|
||||
};
|
||||
|
||||
export type JsonCoverageHistory = {
|
||||
entity: EntityName;
|
||||
history: Array<AggregateCoverage>;
|
||||
};
|
||||
|
||||
export type CoverageHistory = {
|
||||
line: {
|
||||
available: number;
|
||||
covered: number;
|
||||
};
|
||||
branch: BranchHit;
|
||||
};
|
||||
|
||||
export type CoverageMetadata = {
|
||||
vcs: {
|
||||
type: string;
|
||||
location: string;
|
||||
};
|
||||
generationTime: number;
|
||||
};
|
||||
|
||||
export type BranchHit = {
|
||||
covered: number;
|
||||
missed: number;
|
||||
available: number;
|
||||
};
|
||||
|
||||
export type FileEntry = {
|
||||
filename: string;
|
||||
lineHits: Record<number, number>;
|
||||
branchHits: Record<number, BranchHit>;
|
||||
};
|
||||
|
||||
export type AggregateCoverage = {
|
||||
timestamp: number;
|
||||
line: {
|
||||
available: number;
|
||||
covered: number;
|
||||
missed: number;
|
||||
percentage: number;
|
||||
};
|
||||
branch: {
|
||||
available: number;
|
||||
covered: number;
|
||||
missed: number;
|
||||
percentage: number;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user