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
Expand Up @@ -122,6 +122,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
55 changes: 55 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,55 @@
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 = "issues_total";
private static final String METRIC_ETIQUETADOS = "issues_etiquetados";
private static final String RESULT_NAME = "grado_documentacion_issues";

@Override
public ReportItemI<Double> calcIndicator(List<ReportItemI<Double>> metrics) throws NotAvailableMetricException {
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.");
}

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

if (total == 0) {
throw new RuntimeException("El valor de 'issues_total' no puede ser cero.");
}

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);
}
}


@Override
public List<String> requiredMetrics() {
return Arrays.asList(METRIC_TOTAL, METRIC_ETIQUETADOS);
}
}
104 changes: 104 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,104 @@
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");

@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);
}
}

@Override
public ReportItemI getMetric(String metricName, String entityId) throws MetricException {
if (!SUPPORTED_METRICS.contains(metricName)) {
throw new MetricException("Métrica no soportada: " + metricName);
}

if (!entityId.contains("/")) {
entityId = entityId + "/Audit4Improve-API";
}

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());
throw new MetricException("Error al conectar con GitHub: " + e.getMessage());
} catch (Exception e) {
System.err.println("Exception: " + e.getMessage());
throw new MetricException("Error al construir ReportItem: " + e.getMessage());
}
}

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

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

}
35 changes: 33 additions & 2 deletions src/main/resources/a4iDefault.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@
"description": "Numero de issues abiertas",
"unit": "issues"
},
{
"name": "issues_total",
"type": "java.lang.Double",
"description": "Número de issues totales",
"unit": "issues"

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

},
{
"name": "openProjects",
"type": "java.lang.Integer",
Expand Down Expand Up @@ -127,8 +141,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 +289,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 +323,12 @@
"description": "Indicador de conformidad con las convenciones en el repo",
"unit": "ratio"
},
{
"name": "labeledIssues",
"type": "java.lang.Double",
"description": "Número de issues con al menos una etiqueta",
"unit": "issues"
},
{
"name": "teamsBalanceI",
"type": "java.lang.Double",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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<>("issues_total", 10.0).build();
ReportItemI<Double> etiquetados = new ReportItem.ReportItemBuilder<>("issues_etiquetados", 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 faltan métricas")
void testCalcIndicator_MissingMetrics() throws ReportItemException {
IndicatorStrategy<Double> strategy = new GDIStrategy();

ReportItemI<Double> etiquetados = new ReportItem.ReportItemBuilder<>("issues_etiquetados", 7.0).build();

List<ReportItemI<Double>> metrics = List.of(etiquetados);

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

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

assertTrue(required.contains("issues_total"));
assertTrue(required.contains("issues_etiquetados"));
assertEquals(2, required.size(), "Se esperaban exactamente dos métricas");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package us.muit.fs.a4i.test.model.remote;

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

import java.util.List;
import java.util.logging.Logger;

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

import us.muit.fs.a4i.exceptions.MetricException;
import us.muit.fs.a4i.model.entities.ReportItem;
import us.muit.fs.a4i.model.entities.ReportItemI;
import us.muit.fs.a4i.model.remote.ExtraccionMetricas;
import us.muit.fs.a4i.model.remote.GitHubRepositoryEnquirer;
import us.muit.fs.a4i.model.remote.RemoteEnquirer;

class RemoteEnquirerTest {

private static Logger log = Logger.getLogger(RemoteEnquirerTest.class.getName());

private RemoteEnquirer enquirer = new ExtraccionMetricas(); // Asegúrate que esta clase existe y está implementada


// Test que certifica que se puede obtener la métrica "totalIssues" (el resultado no es nulo,
// el nombre de la métrica coincide al completo y el valor es de tipo numérico).

@Test
void testGetTotalIssuesMetric() throws MetricException {
String repoId = "MIT-FS"; // Reemplazar con un repositorio válido de prueba

ReportItemI metric = enquirer.getMetric("totalIssues", repoId);
assertNotNull(metric, "La métrica 'totalIssues' no debe ser null");
assertEquals("totalIssues", metric.getName());
assertTrue(metric.getValue() instanceof Number, "El valor debe ser numérico");

log.info("Total Issues: " + metric.getValue());
}

// Test que certifica que se puede obtener la métrica "labeledIssues" (el resultado no es nulo,
// el nombre de la métrica coincide al completo y el valor es de tipo numérico).

@Test
void testGetLabeledIssuesMetric() throws MetricException {
String repoId = "MIT-FS";

ReportItemI metric = enquirer.getMetric("labeledIssues", repoId);
assertNotNull(metric, "La métrica 'labeledIssues' no debe ser null");
assertEquals("labeledIssues", metric.getName());
assertTrue(metric.getValue() instanceof Number, "El valor debe ser numérico");

log.info("Issues con etiquetas: " + metric.getValue());
}


// Test que verifica que el objeto informa correctamente de las métricas que soporta (que
// la lista no es nula y que dentro de las posibilidades están "totalIssues" y "labeledIssues")

@Test
void testGetAvailableMetrics() {
List<String> metrics = enquirer.getAvailableMetrics();
assertNotNull(metrics);
assertTrue(metrics.contains("totalIssues"), "Debe contener la métrica 'totalIssues'");
assertTrue(metrics.contains("labeledIssues"), "Debe contener la métrica 'labeledIssues'");
}

// Test que intenta pedir una métrica que no existe, para comprobar que la excepción salta como se espera


@Test
void testInvalidMetricThrowsException() {
String repoId = "Isabel-Roman";

assertThrows(MetricException.class, () -> {
enquirer.getMetric("nonExistentMetric", repoId);
});
}


// Test que verifica que el tipo de la clase está correctamente identificado como GITHUB

@Test
void testGetRemoteType() {
assertEquals(RemoteEnquirer.RemoteType.GITHUB, enquirer.getRemoteType());
}
}
Loading