Skip to content

Commit

Permalink
Merge pull request #6 from db1group/feature/add-technical-debt-indicator
Browse files Browse the repository at this point in the history
Feature/add technical debt indicator
  • Loading branch information
AlexandroHervis authored Aug 9, 2024
2 parents fbac238 + 01d4b1a commit 1a23411
Show file tree
Hide file tree
Showing 37 changed files with 336 additions and 123 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@
"collectCoverageFrom": [
"src/**/*.ts",
"!src/infra/**",
"!src/api/**",
"!src/main.ts",
"!src/app.module.ts"
"!src/**/*.module.ts",
"!src/**/*.controller.ts",
"!src/**/*.dto.ts"
],
"coverageReporters": [
"json",
Expand Down
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ sonar.tests=test
sonar.language=ts

# Configuração do caminho de exclusão da cobertura (excluindo a pasta 'infra')
sonar.coverage.exclusions=**/infra/**/*, **/api/**/*, **/src/main.ts
sonar.coverage.exclusions=**/infra/**/*, **/src/main.ts, **/*.module.ts , **/*.controller.ts , **/*.dto.ts

# Diretório onde os relatórios de cobertura são gerados
sonar.javascript.lcov.reportPaths=coverage/lcov.info
Expand Down
7 changes: 4 additions & 3 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Module } from '@nestjs/common';
import { AppController } from './api/app.controller';

import { ConfigModule } from '@nestjs/config';
import { LOGGER } from './infra/logger/logger';
import { ConsoleLogger } from './infra/logger/api-logger';
import { HealthScoreModule } from './modules/health-score/health-score.module';

@Module({
imports: [ConfigModule.forRoot()],
controllers: [AppController],
imports: [ConfigModule.forRoot(), HealthScoreModule],
controllers: [],
providers: [
{
provide: LOGGER,
Expand Down
29 changes: 24 additions & 5 deletions src/application/code-quality.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ import {
CompanyMeasureUsecase,
} from './usecases/company-measure';

import { QualityProvider } from '../domain/quality-provider';
import { QualityProvider } from './quality-provider';
import { TechnicalDebtProvider } from './technical-debt-provider';
import { QualityMeasureResultDto } from '@/modules/health-score/quality-measure-result.dto';

export class CodeQualityService {
constructor(private readonly qualityProvider: QualityProvider) {}
async generateQualityMeasure(keys?: string, keyName?: string): Promise<any> {
constructor(
private readonly qualityProvider: QualityProvider,
private readonly technicalDebtProvider: TechnicalDebtProvider,
) { }
async generateQualityMeasure(
keys?: string,
keyName?: string,
): Promise<QualityMeasureResultDto> {
const projectKeys = await this.getProjectKeysByKeyName(keys, keyName);

const companyMeasureInput: CompanyMeasureInput = {
Expand All @@ -17,10 +25,21 @@ export class CodeQualityService {
const companyData = new CompanyMeasureUsecase(
companyMeasureInput,
this.qualityProvider,
this.technicalDebtProvider,
);

const healthScore = await companyData.execute();

return {
value: await companyData.execute(),
projects: projectKeys,
value: healthScore.value,
technicalDebt: healthScore.technicalDebt,
items: healthScore.items.map((item) => {
return {
project: item.project,
technicalDebt: item.technicalDebt,
value: item.value,
};
})
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { QualityMeasures } from './measures/QualityMeasures';
import { QualityMeasures } from '../domain/health-score/measures/QualityMeasures';

export interface QualityProvider {
getMetricsDataFromProjectKey(projectKey: string): Promise<QualityMeasures[]>;
Expand Down
5 changes: 5 additions & 0 deletions src/application/technical-debt-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { TechnicalDebt } from '@/domain/technical-debt/TechnicalDebt';

export interface TechnicalDebtProvider {
getTechnicalDebtFromProject(project: string): Promise<TechnicalDebt>;
}
23 changes: 16 additions & 7 deletions src/application/usecases/company-measure.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { CompanyHealthCalculator } from '@/domain/CompanyHealthScore';
import { Project } from '../../domain/Project';
import { QualityProvider } from 'src/domain/quality-provider';
import { Company } from '@/domain/Company';
import { QualityProvider } from '../quality-provider';
import { TechnicalDebtProvider } from '../technical-debt-provider';
import { Project } from '@/domain/Project';
import { HealthScore } from '@/domain/HealthScore';

export class CompanyMeasureUsecase {
constructor(
private readonly input: CompanyMeasureInput,
private readonly qualityProviderService: QualityProvider,
private readonly tecnicalDebtService: TechnicalDebtProvider,
) {}

async execute(): Promise<any> {
async execute(): Promise<HealthScore> {
const { projectKeys } = this.input;
const projects = await this.getProjects(projectKeys);
const companyHealthCalculator = new CompanyHealthCalculator(projects);
return companyHealthCalculator.calculateHealthScore();
const company = new Company(projects);
const healthScore = company.calculateHealthScore();

return healthScore;
}

private async getProjects(projectKeys: string[]): Promise<Project[]> {
Expand All @@ -23,7 +28,11 @@ export class CompanyMeasureUsecase {
projectKey,
);
if (qualityMeasures) {
return new Project(qualityMeasures);
const technicalDebt =
await this.tecnicalDebtService.getTechnicalDebtFromProject(
projectKey,
);
return new Project(projectKey, qualityMeasures, technicalDebt);
}
}),
);
Expand Down
47 changes: 47 additions & 0 deletions src/domain/Company.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { HealthScore, HealthScoreItem } from './HealthScore';
import { Project } from './Project';
import { NoProjectError } from './health-score/exceptions/InvalidProjectError';

export class Company {
private readonly projects: Project[];

constructor(projects: Project[]) {
this.projects = projects;
if (this.projects.length === 0) {
throw new NoProjectError();
}
}

public calculateHealthScore(): HealthScore {
const items = this.calculateItems();

const acummulatedScore = items.reduce((acc, item) => {
return acc + item.value
}, 0);

const healthScoreRounded =
Math.round((acummulatedScore / this.projects.length) * 100) / 100;

const acummulatedDebt = items.reduce((acc, item) => {
return acc + item.technicalDebt;
}, 0);

return {
value: healthScoreRounded,
technicalDebt: acummulatedDebt,
items,
}
}

private calculateItems(): HealthScoreItem[] {
return this.projects.map((project) => {
const technicalDebt = project.calculateTechnicalDebt();
const value = project.calculateHealthScore();
return {
project: project.name,
technicalDebt,
value,
};
});
}
}
23 changes: 0 additions & 23 deletions src/domain/CompanyHealthScore.ts

This file was deleted.

11 changes: 11 additions & 0 deletions src/domain/HealthScore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class HealthScore {
value: number;
technicalDebt: number;
items: HealthScoreItem[];
}

export class HealthScoreItem {
project: string;
technicalDebt: number;
value: number;
}
13 changes: 11 additions & 2 deletions src/domain/Project.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { QualityMeasures } from './measures/QualityMeasures';
import { QualityMeasures } from './health-score/measures/QualityMeasures';
import { TechnicalDebt } from './technical-debt/TechnicalDebt';

export class Project {
constructor(private readonly qualityMeasures: QualityMeasures[]) {}
constructor(
public readonly name: string,
private readonly qualityMeasures: QualityMeasures[],
private readonly technicalDebt: TechnicalDebt,
) { }

calculateHealthScore(): number {
return this.qualityMeasures.reduce((acc, qualityMeasure) => {
return acc + qualityMeasure.getQualityRatio();
}, 0);
}

calculateTechnicalDebt(): number {
return this.technicalDebt.calculateTechnicalDebt();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export interface QualityMeasures {
getQualityRatio(): number;
}

export const TECHICAL_DEBT = 'sqale_index';

export enum QualityMeasuresTypes {
RELIABILITY_RATING = 'reliability_rating',
MANUTENABILITY_RATING = 'sqale_rating',
Expand Down
File renamed without changes.
9 changes: 9 additions & 0 deletions src/domain/technical-debt/TechnicalDebt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export class TechnicalDebt {
constructor(private readonly value: number) {}

calculateTechnicalDebt(): number {
const hour = 60;
const technicalDebtInHours = this.value / hour;
return technicalDebtInHours;
}
}
31 changes: 26 additions & 5 deletions src/infra/sonar/sonar-http-client.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
import { HttpClient } from '../http/http-client';
import { QualityMeasures } from '../../domain/measures/QualityMeasures';
import { SonarAdapterMetrics } from './sonar-metrics-adapter';
import { QualityProvider } from '@/domain/quality-provider';
import {
QualityMeasures,
TECHICAL_DEBT,
} from '../../domain/health-score/measures/QualityMeasures';

export class SonarHttpClient implements QualityProvider {
import { QualityProvider } from '@/application/quality-provider';
import { SonarMetricsAdapter } from './sonar-metrics-adapter';
import { TechnicalDebtProvider } from '@/application/technical-debt-provider';
import { TechnicalDebt } from '@/domain/technical-debt/TechnicalDebt';
import { SonarTechicalDebtAdapter } from './sonar-techical-debt-adapter';

export class SonarHttpClient implements QualityProvider, TechnicalDebtProvider {
constructor(private readonly httpClient: HttpClient) {}
getTechnicalDebtFromProject(project: string): Promise<TechnicalDebt> {
const request = this.httpClient.get('/api/measures/component', {
params: {
component: project,
metricKeys: TECHICAL_DEBT,
},
});

return request.then((data: SonarMetricResponse) => {
if (data.component.measures.length) {
return new SonarTechicalDebtAdapter(data).execute();
}
});
}
getProjectsFromProjectKeyName(projectKeyName: string): Promise<string[]> {
const request = this.httpClient.get('/api/projects/search', {
params: {
Expand All @@ -29,7 +50,7 @@ export class SonarHttpClient implements QualityProvider {

return request.then((data: SonarMetricResponse) => {
if (data.component.measures.length) {
return new SonarAdapterMetrics(data).execute();
return new SonarMetricsAdapter(data).execute();
}
});
}
Expand Down
16 changes: 8 additions & 8 deletions src/infra/sonar/sonar-metrics-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import {
QualityMeasures,
QualityMeasuresTypes,
} from '../../domain/measures/QualityMeasures';
} from '../../domain/health-score/measures/QualityMeasures';
import { SonarMetricResponse } from './sonar-http-client';
import { ReliabilityMeasure } from '../../domain/measures/ReliabilityMeasure';
import { CoverageQualityMeasure } from '../../domain/measures/CoverageQualityMeasure';
import { ManutenabilityMeasure } from '../../domain/measures/ManutenabilityMeasure';
import { SecurityRatingMeasure } from '../../domain/measures/SecurityRatingMeasure';
import { DuplicatedLinesMeasure } from '../../domain/measures/DuplicatedLinesMeasure';
import { SecurityReviewMeasure } from '../../domain/measures/SecurityReviewMeasure';
import { ReliabilityMeasure } from '../../domain/health-score/measures/ReliabilityMeasure';
import { CoverageQualityMeasure } from '../../domain/health-score/measures/CoverageQualityMeasure';
import { ManutenabilityMeasure } from '../../domain/health-score/measures/ManutenabilityMeasure';
import { SecurityRatingMeasure } from '../../domain/health-score/measures/SecurityRatingMeasure';
import { DuplicatedLinesMeasure } from '../../domain/health-score/measures/DuplicatedLinesMeasure';
import { SecurityReviewMeasure } from '../../domain/health-score/measures/SecurityReviewMeasure';

export class SonarAdapterMetrics {
export class SonarMetricsAdapter {
constructor(private readonly sonarResponse: SonarMetricResponse) {}
execute(): QualityMeasures[] {
const healthPanelMetrics = Object.values(QualityMeasuresTypes);
Expand Down
17 changes: 17 additions & 0 deletions src/infra/sonar/sonar-techical-debt-adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { TECHICAL_DEBT } from '../../domain/health-score/measures/QualityMeasures';
import { SonarMetricResponse } from './sonar-http-client';

import { TechnicalDebt } from '@/domain/technical-debt/TechnicalDebt';

export class SonarTechicalDebtAdapter {
constructor(private readonly sonarResponse: SonarMetricResponse) {}
execute(): TechnicalDebt {
const sonarMeasures = this.sonarResponse.component.measures;

const technicalDebtValue = sonarMeasures.find(
(sonarMeasure) => sonarMeasure.metric === TECHICAL_DEBT,
);

return new TechnicalDebt(Number(technicalDebtValue.value));
}
}
24 changes: 1 addition & 23 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

import cluster from 'cluster';
import os from 'os';

import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

const port = process.env.PORT || 3000;
Expand All @@ -19,23 +16,4 @@ async function bootstrap() {
await app.listen(port);
}

export class AppClusterService {
static clusterize(callback: Function): void {
const numCPUs = os.cpus().length;

if (cluster.isPrimary) {
console.log(`Master server started on ${process.pid}`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker) => {
console.log(`Worker ${worker.process.pid} died. Restarting`);
cluster.fork();
});
} else {
console.log(`Cluster server started on ${process.pid}`);
callback();
}
}
}
AppClusterService.clusterize(bootstrap);
bootstrap();
Loading

0 comments on commit 1a23411

Please sign in to comment.