@@ -15,5 +15,3 @@
|
||||
*/
|
||||
|
||||
export * from './service/router';
|
||||
|
||||
export * from './service/jsoncoverage-types';
|
||||
|
||||
@@ -13,17 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
getVoidLogger,
|
||||
SingleConnectionDatabaseManager,
|
||||
} from '@backstage/backend-common';
|
||||
import { SingleConnectionDatabaseManager } from '@backstage/backend-common';
|
||||
import { stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
CodeCoverageDatabase,
|
||||
CodeCoverageStore,
|
||||
} from './CodeCoverageDatabase';
|
||||
import { JsonCodeCoverage } from './jsoncoverage-types';
|
||||
import { JsonCodeCoverage } from './types';
|
||||
|
||||
const db = SingleConnectionDatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
@@ -103,10 +100,9 @@ let database: CodeCoverageStore;
|
||||
describe('CodeCoverageDatabase', () => {
|
||||
beforeAll(async () => {
|
||||
const client = await db.getClient();
|
||||
database = await CodeCoverageDatabase.create(client, getVoidLogger());
|
||||
database.insertCodeCoverage(coverage[0]);
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
database.insertCodeCoverage(coverage[1]);
|
||||
database = await CodeCoverageDatabase.create(client);
|
||||
await database.insertCodeCoverage(coverage[0]);
|
||||
await database.insertCodeCoverage(coverage[1]);
|
||||
});
|
||||
|
||||
describe('insertCodeCoverage', () => {
|
||||
|
||||
@@ -18,15 +18,15 @@ import { NotFoundError } from '@backstage/errors';
|
||||
import { parseEntityName, stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { Knex } from 'knex';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { Logger } from 'winston';
|
||||
import { aggregateCoverage } from './CoverageUtils';
|
||||
import { JsonCodeCoverage, JsonCoverageHistory } from './jsoncoverage-types';
|
||||
import { JsonCodeCoverage, JsonCoverageHistory } from './types';
|
||||
|
||||
export type RawDbCoverageRow = {
|
||||
id: string;
|
||||
entity: string;
|
||||
coverage: string;
|
||||
};
|
||||
|
||||
export interface CodeCoverageStore {
|
||||
insertCodeCoverage(
|
||||
coverage: JsonCodeCoverage,
|
||||
@@ -39,44 +39,49 @@ const migrationsDir = resolvePackagePath(
|
||||
'@backstage/plugin-code-coverage-backend',
|
||||
'migrations',
|
||||
);
|
||||
|
||||
export class CodeCoverageDatabase implements CodeCoverageStore {
|
||||
static async create(knex: Knex, logger: Logger): Promise<CodeCoverageStore> {
|
||||
static async create(knex: Knex): Promise<CodeCoverageStore> {
|
||||
await knex.migrate.latest({
|
||||
directory: migrationsDir,
|
||||
});
|
||||
return new CodeCoverageDatabase(knex, logger);
|
||||
return new CodeCoverageDatabase(knex);
|
||||
}
|
||||
|
||||
constructor(private readonly db: Knex, private readonly logger: Logger) {}
|
||||
constructor(private readonly db: Knex) {}
|
||||
|
||||
async insertCodeCoverage(
|
||||
coverage: JsonCodeCoverage,
|
||||
): Promise<{ codeCoverageId: string }> {
|
||||
const codeCoverageId = uuid();
|
||||
this.logger.error(JSON.stringify(coverage.entity));
|
||||
const entity = stringifyEntityRef({
|
||||
kind: coverage.entity.kind,
|
||||
namespace: coverage.entity.namespace,
|
||||
name: coverage.entity.name,
|
||||
});
|
||||
|
||||
await this.db<RawDbCoverageRow>('code_coverage').insert({
|
||||
id: codeCoverageId,
|
||||
entity: entity,
|
||||
coverage: JSON.stringify(coverage),
|
||||
});
|
||||
|
||||
return { codeCoverageId };
|
||||
}
|
||||
|
||||
async getCodeCoverage(entity: string): Promise<JsonCodeCoverage> {
|
||||
const [result] = await this.db<RawDbCoverageRow>('code_coverage')
|
||||
.where({ entity: entity })
|
||||
.orderBy('created_at', 'desc')
|
||||
.orderBy('index', 'desc')
|
||||
.limit(1)
|
||||
.select();
|
||||
|
||||
if (!result) {
|
||||
throw new NotFoundError(
|
||||
`No coverage for entity '${JSON.stringify(entity)}' found`,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(result.coverage);
|
||||
} catch (error) {
|
||||
@@ -90,7 +95,7 @@ export class CodeCoverageDatabase implements CodeCoverageStore {
|
||||
): Promise<JsonCoverageHistory> {
|
||||
const res = await this.db<RawDbCoverageRow>('code_coverage')
|
||||
.where({ entity: entity })
|
||||
.orderBy('created_at', 'desc')
|
||||
.orderBy('index', 'desc')
|
||||
.limit(limit)
|
||||
.select();
|
||||
|
||||
@@ -99,6 +104,7 @@ export class CodeCoverageDatabase implements CodeCoverageStore {
|
||||
.map(c => aggregateCoverage(c));
|
||||
|
||||
const entityName = parseEntityName(entity);
|
||||
|
||||
return {
|
||||
entity: {
|
||||
name: entityName.name,
|
||||
|
||||
@@ -22,11 +22,7 @@ import {
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ScmIntegration, ScmIntegrations } from '@backstage/integration';
|
||||
import {
|
||||
AggregateCoverage,
|
||||
FileEntry,
|
||||
JsonCodeCoverage,
|
||||
} from './jsoncoverage-types';
|
||||
import { AggregateCoverage, FileEntry, JsonCodeCoverage } from './types';
|
||||
|
||||
export const calculatePercentage = (
|
||||
available: number,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { FileEntry } from '../jsoncoverage-types';
|
||||
import { FileEntry } from '../types';
|
||||
|
||||
export interface Converter {
|
||||
convert(xml: unknown, scmFiles: Array<string>): Array<FileEntry>;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { BranchHit, FileEntry } from '../jsoncoverage-types';
|
||||
import { BranchHit, FileEntry } from '../types';
|
||||
import { CoberturaXML, InnerClass, LineHit } from './types';
|
||||
import { Logger } from 'winston';
|
||||
import { Converter } from './Converter';
|
||||
@@ -63,7 +63,7 @@ export class Cobertura implements Converter {
|
||||
});
|
||||
|
||||
const currentFile = scmFiles.find(f => f.endsWith(packageAndFilename));
|
||||
this.logger.info(`matched ${packageAndFilename} to ${currentFile}`);
|
||||
this.logger.debug(`matched ${packageAndFilename} to ${currentFile}`);
|
||||
if (
|
||||
scmFiles.length === 0 ||
|
||||
(Object.keys(lineHits).length > 0 && currentFile)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { BranchHit, FileEntry } from '../jsoncoverage-types';
|
||||
import { BranchHit, FileEntry } from '../types';
|
||||
import { JacocoSourceFile, JacocoXML } from './types';
|
||||
import { Logger } from 'winston';
|
||||
import { Converter } from './Converter';
|
||||
@@ -35,7 +35,7 @@ export class Jacoco implements Converter {
|
||||
* Converts jacoco into shared json coverage format
|
||||
*
|
||||
* @param xml jacoco xml object
|
||||
* @param scmFiles list of files that are commited to SCM
|
||||
* @param scmFiles list of files that are committed to SCM
|
||||
*/
|
||||
convert(xml: JacocoXML, scmFiles: Array<string>): Array<FileEntry> {
|
||||
const jscov: Array<FileEntry> = [];
|
||||
@@ -64,7 +64,7 @@ export class Jacoco implements Converter {
|
||||
|
||||
const packageAndFilename = `${packageName}/${fileName}`;
|
||||
const currentFile = scmFiles.find(f => f.endsWith(packageAndFilename));
|
||||
this.logger.info(`matched ${packageAndFilename} to ${currentFile}`);
|
||||
this.logger.debug(`matched ${packageAndFilename} to ${currentFile}`);
|
||||
if (Object.keys(lineHits).length > 0 && currentFile) {
|
||||
jscov.push({
|
||||
filename: currentFile,
|
||||
|
||||
@@ -58,7 +58,6 @@ export const makeRouter = async (
|
||||
|
||||
const codeCoverageDatabase = await CodeCoverageDatabase.create(
|
||||
await database.getClient(),
|
||||
logger,
|
||||
);
|
||||
const codecovUrl = await discovery.getExternalBaseUrl('code-coverage');
|
||||
const catalogApi = new CatalogClient({ discoveryApi: discovery });
|
||||
@@ -147,8 +146,8 @@ export const makeRouter = async (
|
||||
const scmTree = await urlReader.readTree(sourceLocation.target);
|
||||
const scmFile = (await scmTree.files()).find(f => f.path === path);
|
||||
if (!scmFile) {
|
||||
res.status(400).json({
|
||||
message: "couldn't find file in SCM",
|
||||
res.status(404).json({
|
||||
message: "Couldn't find file in SCM",
|
||||
file: path,
|
||||
scm: vcs.title,
|
||||
});
|
||||
@@ -157,7 +156,7 @@ export const makeRouter = async (
|
||||
const content = await scmFile?.content();
|
||||
if (!content) {
|
||||
res.status(400).json({
|
||||
message: "couldn't process content of file in SCM",
|
||||
message: "Couldn't process content of file in SCM",
|
||||
file: path,
|
||||
scm: vcs.title,
|
||||
});
|
||||
@@ -187,7 +186,7 @@ export const makeRouter = async (
|
||||
} else if (coverageType === 'cobertura') {
|
||||
converter = new Cobertura(logger);
|
||||
} else {
|
||||
throw new NotFoundError(`unsupported coverage type '${coverageType}`);
|
||||
throw new InputError(`Unsupported coverage type '${coverageType}`);
|
||||
}
|
||||
|
||||
const {
|
||||
|
||||
@@ -22,9 +22,9 @@ import {
|
||||
useHotMemoize,
|
||||
} from '@backstage/backend-common';
|
||||
import { Server } from 'http';
|
||||
import knexFactory from 'knex';
|
||||
import { Logger } from 'winston';
|
||||
import { createRouter } from './router';
|
||||
import { DatabaseManager } from '@backstage/plugin-catalog-backend';
|
||||
|
||||
export interface ServerOptions {
|
||||
port: number;
|
||||
@@ -38,13 +38,23 @@ export async function startStandaloneServer(
|
||||
const logger = options.logger.child({ service: 'code-coverage-backend' });
|
||||
const config = await loadBackendConfig({ logger, argv: process.argv });
|
||||
|
||||
const db = useHotMemoize(module, () =>
|
||||
DatabaseManager.createInMemoryDatabaseConnection(),
|
||||
);
|
||||
const db = useHotMemoize(module, () => {
|
||||
const knex = knexFactory({
|
||||
client: 'sqlite3',
|
||||
connection: ':memory:',
|
||||
useNullAsDefault: true,
|
||||
});
|
||||
|
||||
knex.client.pool.on('createSuccess', (_eventId: any, resource: any) => {
|
||||
resource.run('PRAGMA foreign_keys = ON', () => {});
|
||||
});
|
||||
|
||||
return knex;
|
||||
});
|
||||
|
||||
logger.debug('Starting application server...');
|
||||
const router = await createRouter({
|
||||
database: { getClient: () => db },
|
||||
database: { getClient: async () => db },
|
||||
config,
|
||||
discovery: SingleHostDiscovery.fromConfig(config),
|
||||
urlReader: UrlReaders.default({ logger, config }),
|
||||
|
||||
Reference in New Issue
Block a user