Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
*
* Código revisado por Sergio Ramírez.
*/

package us.muit.fs.a4i.config;

import java.io.FileInputStream;
Expand Down Expand Up @@ -122,6 +123,7 @@ private HashMap<String, String> isDefinedIndicator(String indicatorName, String
int warningLimit = 0;
int criticalLimit = 0;


if (limits != null) {
okLimit = limits.getInt("ok");
warningLimit = limits.getInt("warning");
Expand Down
73 changes: 73 additions & 0 deletions src/main/java/us/muit/fs/a4i/control/GDIStrategy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package us.muit.fs.a4i.control;

import us.muit.fs.a4i.exceptions.NotAvailableMetricException;
import us.muit.fs.a4i.exceptions.ReportItemException;
import us.muit.fs.a4i.model.entities.ReportItem;
import us.muit.fs.a4i.model.entities.ReportItemI;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class GDIStrategy implements IndicatorStrategy<Double> {

private static final String METRIC_TOTAL = "totalIssues";
private static final String METRIC_ETIQUETADOS = "labeledIssues";
private static final String RESULT_NAME = "grado_documentacion_issues";

@Override
public ReportItemI<Double> calcIndicator(List<ReportItemI<Double>> metrics) throws NotAvailableMetricException {
if (metrics == null) {
throw new NotAvailableMetricException("No se han proporcionado métricas.");
}

Optional<ReportItemI<Double>> totalOpt = metrics.stream()
.filter(m -> METRIC_TOTAL.equals(m.getName()))
.findFirst();

Optional<ReportItemI<Double>> etiquetadosOpt = metrics.stream()
.filter(m -> METRIC_ETIQUETADOS.equals(m.getName()))
.findFirst();

if (totalOpt.isEmpty() || etiquetadosOpt.isEmpty()) {
throw new NotAvailableMetricException("Faltan métricas necesarias para calcular el indicador GDI.");
}

double total = totalOpt.get().getValue();
double etiquetados = etiquetadosOpt.get().getValue();

if (total <= 0) {
throw new IllegalArgumentException(
"El valor de 'totalIssues' debe ser mayor que cero."
);
}

if (etiquetados < 0) {
throw new IllegalArgumentException(
"El valor de 'labeledIssues' no puede ser negativo."
);
}

if (etiquetados > total) {
throw new IllegalArgumentException(
"El número de labeledIssues no puede superar el número totalIssues."
);
}

double resultado = (etiquetados / total) * 100.0;

try {
// Intentamos crear el ReportItem y si algo falla, envolvemos la excepción.
return new ReportItem.ReportItemBuilder<>(RESULT_NAME, resultado).build();
} catch (ReportItemException e) {
// Envolvemos la ReportItemException en una RuntimeException
throw new RuntimeException("Error al crear el ReportItem: " + e.getMessage(), e);

Check warning on line 64 in src/main/java/us/muit/fs/a4i/control/GDIStrategy.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=MIT-FS_Audit4Improve-API&issues=AZ4IiyhtqhWZoRilzobs&open=AZ4IiyhtqhWZoRilzobs&pullRequest=266
}
}


@Override
public List<String> requiredMetrics() {
return Arrays.asList(METRIC_TOTAL, METRIC_ETIQUETADOS);
}
}
108 changes: 108 additions & 0 deletions src/main/java/us/muit/fs/a4i/model/remote/ExtraccionMetricas.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Código revisado por Sergio Ramírez.
*/

package us.muit.fs.a4i.model.remote;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import org.kohsuke.github.GHIssue;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GitHub;
import org.kohsuke.github.GitHubBuilder;

import us.muit.fs.a4i.exceptions.MetricException;
import us.muit.fs.a4i.model.entities.ReportItemI;
import us.muit.fs.a4i.model.entities.ReportItem.ReportItemBuilder;
import us.muit.fs.a4i.model.entities.Report;
import us.muit.fs.a4i.model.entities.ReportI;


public class ExtraccionMetricas implements RemoteEnquirer {

private static final List<String> SUPPORTED_METRICS = Arrays.asList("totalIssues", "labeledIssues");

Check failure on line 25 in src/main/java/us/muit/fs/a4i/model/remote/ExtraccionMetricas.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "totalIssues" 3 times.

See more on https://sonarcloud.io/project/issues?id=MIT-FS_Audit4Improve-API&issues=AZ4Iiye2qhWZoRilzobn&open=AZ4Iiye2qhWZoRilzobn&pullRequest=266

Check failure on line 25 in src/main/java/us/muit/fs/a4i/model/remote/ExtraccionMetricas.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "labeledIssues" 3 times.

See more on https://sonarcloud.io/project/issues?id=MIT-FS_Audit4Improve-API&issues=AZ4Iiye2qhWZoRilzobo&open=AZ4Iiye2qhWZoRilzobo&pullRequest=266

@Override
public ReportI buildReport(String entityId) {
try {
Report report = new Report(ReportI.ReportType.REPOSITORY, entityId);

for (String metric : getAvailableMetrics()) {
ReportItemI<?> item = getMetric(metric, entityId);
report.addMetric(item);
}

return report;

} catch (MetricException e) {
throw new RuntimeException("Error al construir el informe: " + e.getMessage(), e);

Check warning on line 40 in src/main/java/us/muit/fs/a4i/model/remote/ExtraccionMetricas.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=MIT-FS_Audit4Improve-API&issues=AZ4Iiye2qhWZoRilzobm&open=AZ4Iiye2qhWZoRilzobm&pullRequest=266
}
}

@Override
public ReportItemI getMetric(String metricName, String entityId) throws MetricException {

Check failure on line 45 in src/main/java/us/muit/fs/a4i/model/remote/ExtraccionMetricas.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=MIT-FS_Audit4Improve-API&issues=AZ4Iiye2qhWZoRilzobp&open=AZ4Iiye2qhWZoRilzobp&pullRequest=266
if (!SUPPORTED_METRICS.contains(metricName)) {
throw new MetricException("Métrica no soportada: " + metricName);
}

if (entityId == null || entityId.isBlank() || !entityId.contains("/")) {
throw new MetricException("El identificador del repositorio debe tener formato owner/repository");
}

try {
GitHub github;
String token = System.getenv("GITHUB_PACKAGES");

if (token != null && !token.isEmpty()) {
github = new GitHubBuilder().withOAuthToken(token).build();
} else {
github = GitHub.connectAnonymously();
}
GHRepository repo = github.getRepository(entityId);

int total = 0;
int etiquetados = 0;

for (GHIssue issue : repo.getIssues(org.kohsuke.github.GHIssueState.OPEN)) {
if (!issue.isPullRequest()) {
total++;
if (!issue.getLabels().isEmpty()) {
etiquetados++;
}
}
}

if (metricName.equals("totalIssues")) {
return new ReportItemBuilder<Double>("totalIssues", (double) total)
.source("GitHub")
.build();
} else if (metricName.equals("labeledIssues")) {
return new ReportItemBuilder<Double>("labeledIssues", (double) etiquetados)
.source("GitHub")
.build();
} else {
throw new MetricException("Métrica desconocida: " + metricName);
}

} catch (IOException e) {
System.err.println("IOException: " + e.getMessage());

Check warning on line 90 in src/main/java/us/muit/fs/a4i/model/remote/ExtraccionMetricas.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this use of System.err by a logger.

See more on https://sonarcloud.io/project/issues?id=MIT-FS_Audit4Improve-API&issues=AZ4Iiye2qhWZoRilzobq&open=AZ4Iiye2qhWZoRilzobq&pullRequest=266
throw new MetricException("Error al conectar con GitHub: " + e.getMessage());
} catch (Exception e) {
System.err.println("Exception: " + e.getMessage());

Check warning on line 93 in src/main/java/us/muit/fs/a4i/model/remote/ExtraccionMetricas.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this use of System.err by a logger.

See more on https://sonarcloud.io/project/issues?id=MIT-FS_Audit4Improve-API&issues=AZ4Iiye2qhWZoRilzobr&open=AZ4Iiye2qhWZoRilzobr&pullRequest=266
throw new MetricException("Error al construir ReportItem: " + e.getMessage());
}
}

@Override
public List<String> getAvailableMetrics() {
return SUPPORTED_METRICS;
}

@Override
public RemoteType getRemoteType() {
return RemoteType.GITHUB;
}

}
24 changes: 22 additions & 2 deletions src/main/resources/a4iDefault.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@
"description": "Numero de issues abiertas",
"unit": "issues"
},

{
"name": "labeledIssues",
"type": "java.lang.Double",
"description": "Número de issues etiquetados",
"unit": "issues"

},
{
"name": "openProjects",
"type": "java.lang.Integer",
Expand Down Expand Up @@ -127,8 +135,8 @@
"unit": "issues"
},
{
"name": "issues",
"type": "java.lang.Integer",
"name": "totalIssues",
"type": "java.lang.Double",
"description": "Tareas totales",
"unit": "issues"
},
Expand Down Expand Up @@ -275,6 +283,17 @@
"critical": 25
}
},
{
"name": "grado_documentacion_issues",
"type": "java.lang.Double",
"description": "% Issues etiquetados frente a totales",
"unit": "%",
"limits": {
"ok": 71,
"warning": 51,
"critical": 31
}
},
{
"name": "developerPerfomance",
"type": "java.lang.Double",
Expand All @@ -298,6 +317,7 @@
"description": "Indicador de conformidad con las convenciones en el repo",
"unit": "ratio"
},

{
"name": "teamsBalanceI",
"type": "java.lang.Double",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package us.muit.fs.a4i.test.control;

import static org.junit.jupiter.api.Assertions.*;

import us.muit.fs.a4i.exceptions.NotAvailableMetricException;
import us.muit.fs.a4i.exceptions.ReportItemException;
import us.muit.fs.a4i.model.entities.ReportItem;
import us.muit.fs.a4i.model.entities.ReportItemI;
import us.muit.fs.a4i.control.GDIStrategy;
import us.muit.fs.a4i.control.IndicatorStrategy;

import java.util.Arrays;
import java.util.List;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class IndicatorStrategyTest {

@Test
@DisplayName("Test correcto del cálculo GDI con métricas válidas")
void testCalcIndicator_OK() throws NotAvailableMetricException, ReportItemException {
IndicatorStrategy<Double> strategy = new GDIStrategy();

ReportItemI<Double> total = new ReportItem.ReportItemBuilder<>("totalIssues", 10.0).build();
ReportItemI<Double> etiquetados = new ReportItem.ReportItemBuilder<>("labeledIssues", 7.0).build();

List<ReportItemI<Double>> metrics = Arrays.asList(total, etiquetados);

ReportItemI<Double> result = strategy.calcIndicator(metrics);
assertEquals(70.0, result.getValue(), 0.001, "El cálculo del GDI es incorrecto");
assertEquals("grado_documentacion_issues", result.getName(), "El nombre del resultado no es el esperado");
}

@Test
@DisplayName("Test de excepción cuando falta la métrica totalIssues")
void testCalcIndicator_MissingTotalIssues() throws ReportItemException {
IndicatorStrategy<Double> strategy = new GDIStrategy();

ReportItemI<Double> etiquetados = new ReportItem.ReportItemBuilder<>("labeledIssues", 7.0).build();
List<ReportItemI<Double>> metrics = List.of(etiquetados);

assertThrows(NotAvailableMetricException.class, () -> {
strategy.calcIndicator(metrics);
}, "Se esperaba una excepción por falta de la métrica totalIssues");
}

@Test
@DisplayName("Test de excepción cuando falta la métrica labeledIssues")
void testCalcIndicator_MissingLabeledIssues() throws ReportItemException {
IndicatorStrategy<Double> strategy = new GDIStrategy();

ReportItemI<Double> total = new ReportItem.ReportItemBuilder<>("totalIssues", 10.0).build();
List<ReportItemI<Double>> metrics = List.of(total);

assertThrows(NotAvailableMetricException.class, () -> {
strategy.calcIndicator(metrics);
}, "Se esperaba una excepción por falta de la métrica labeledIssues");
}


@Test
@DisplayName("Test de excepción cuando totalIssues es cero")
void testCalcIndicator_TotalIssuesZero() throws ReportItemException {
IndicatorStrategy<Double> strategy = new GDIStrategy();

ReportItemI<Double> total = new ReportItem.ReportItemBuilder<>("totalIssues", 0.0).build();
ReportItemI<Double> etiquetados = new ReportItem.ReportItemBuilder<>("labeledIssues", 0.0).build();

List<ReportItemI<Double>> metrics = Arrays.asList(total, etiquetados);

assertThrows(IllegalArgumentException.class, () -> {
strategy.calcIndicator(metrics);
}, "Se esperaba una excepción porque totalIssues no puede ser cero");
}

@Test
@DisplayName("Test de requiredMetrics() devuelve métricas necesarias")
void testRequiredMetrics() {
IndicatorStrategy<Double> strategy = new GDIStrategy();
List<String> required = strategy.requiredMetrics();

assertTrue(required.contains("totalIssues"));
assertTrue(required.contains("labeledIssues"));
assertEquals(2, required.size(), "Se esperaban exactamente dos métricas");
}
}
Loading
Loading