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
4 changes: 2 additions & 2 deletions .github/workflows/pruebas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ jobs:
runs-on: ubuntu-latest
env:
GITHUB_LOGIN: ${{ github.actor }}
GITHUB_PACKAGES: ${{ secrets.GHTOKEN }}
GITHUB_OAUTH: ${{ secrets.GHTOKEN }}
GITHUB_PACKAGES: ${{ secrets.GITHUB_TOKEN }}
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Clonando el repositorio y estableciendo el espacio de trabajo
uses: actions/checkout@v3
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,4 @@ test {




83 changes: 83 additions & 0 deletions src/main/java/us/muit/fs/a4i/control/IndicatorCalidadIssues.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package us.muit.fs.a4i.control;

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

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;

/**
* Estrategia para calcular el indicador de calidad de resolución.
*/
public class IndicatorCalidadIssues {

private static final String MRI = "reopenedIssuesAvg";
private static final String TRPI = "firstTryResolutionRate";
private static final String IAPC = "postClosureActivityRate";
private static final String RESULT_NAME = "calidadResolucion";

/**
* Calcula el indicador a partir de una lista de métricas.
*
* @param metrics Lista de métricas
* @return ReportItemI con el valor del indicador
* @throws NotAvailableMetricException si faltan métricas necesarias
*/
public ReportItemI<Double> calcIndicator(List<ReportItemI<Double>> metrics) throws NotAvailableMetricException {
Double mriValue = null;
Double trpiValue = null;
Double iapcValue = null;

for (ReportItemI<Double> item : metrics) {
switch (item.getName()) {
case MRI:
mriValue = item.getValue();
break;
case TRPI:
trpiValue = item.getValue();
break;
case IAPC:
iapcValue = item.getValue();
break;
}
}

if (mriValue == null || trpiValue == null || iapcValue == null) {
throw new NotAvailableMetricException("Faltan métricas requeridas para calcular el indicador.");
}

// Normalización del valor MRI
double percMRI;
if (mriValue <= 0.3) {
percMRI = 0.0;
} else if (mriValue >= 2.0) {
percMRI = 100.0;
} else {
percMRI = (mriValue - 0.3) / 1.7 * 100.0;
}

// Cálculo de la calidad
double quality = 0.3 * (100-percMRI) + 0.4 * trpiValue + 0.3 * (100.0 - iapcValue);


try {
return new ReportItem.ReportItemBuilder<>(RESULT_NAME, quality)
.source("auto")
.unit("%")
.build();
} catch (ReportItemException e) {
throw new RuntimeException("Error al construir ReportItem: " + e.getMessage(), e);
}
}

/**
* Devuelve la lista de métricas requeridas por este indicador.
*
* @return lista de nombres de métricas requeridas
*/
public List<String> requiredMetrics() {
return Arrays.asList(MRI, TRPI, IAPC);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ public interface IndicatorStrategy<T> {
*/
public List<String> requiredMetrics();

}
}
127 changes: 127 additions & 0 deletions src/main/java/us/muit/fs/a4i/model/remote/GitHubRemoteEnquirer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package us.muit.fs.a4i.model.remote;

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


import org.kohsuke.github.*;

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

public class GitHubRemoteEnquirer implements RemoteEnquirer {

private static final List<String> AVAILABLE_METRICS = List.of(
"reopenedIssuesAvg",
"firstTryResolutionRate",
"postClosureActivityRate"
);

private final GitHub github;

public GitHubRemoteEnquirer() throws IOException {
String token = System.getenv("GITHUB_OAUTH");
if (token == null || token.isEmpty()) {
token = System.getenv("GITHUB_TOKEN");
}
if (token == null || token.isEmpty()) {
throw new IllegalStateException("No GitHub token provided.");
}
this.github = new GitHubBuilder().withOAuthToken(token).build();

}

@Override
public ReportI buildReport(String repoFullName) {
Report report = new Report(repoFullName);
for (String metric : AVAILABLE_METRICS) {
try {
report.addMetric(getMetric(metric, repoFullName));
} catch (MetricException e) {
System.err.println("Error al obtener la métrica: " + metric);
}
}
return report;
}

@Override
public ReportItemI getMetric(String metricName, String repoFullName) throws MetricException {
try {
GHRepository repo = github.getRepository(repoFullName);
List<GHIssue> issues = repo.getIssues(GHIssueState.CLOSED);
int reopenedCount = 0;
int firstTrySuccess = 0;
int postClosureActivity = 0;

for (GHIssue issue : issues) {
boolean reopened = false;
for (GHIssueEvent event : issue.listEvents()) {
if (event.getEvent().equals("reopened")) {
reopened = true;
reopenedCount++;
break;
}
}

if (!reopened) {
firstTrySuccess++;
}

if (issue.getUpdatedAt().after(issue.getClosedAt())) {
postClosureActivity++;
}
}

int totalIssues = issues.size();
if (totalIssues == 0) throw new MetricException("No hay issues cerrados para analizar.");

double avgReopened = (double) reopenedCount / totalIssues;
double trpi = ((double) firstTrySuccess / totalIssues) * 100;
double pcap = ((double) postClosureActivity / totalIssues) * 100;

try {
switch (metricName) {
case "reopenedIssuesAvg":
return new ReportItemBuilder<Double>(metricName, avgReopened)
.source("GitHub")
.build();

case "firstTryResolutionRate":
return new ReportItemBuilder<Double>(metricName, trpi)
.source("GitHub")
.build();

case "postClosureActivityRate":
return new ReportItemBuilder<Double>(metricName, pcap)
.source("GitHub")
.build();

default:
throw new MetricException("Métrica no definida: " + metricName);
}

} catch (ReportItemException e) {
throw new MetricException("Error al construir el ReportItem para " + metricName + ": " + e.getMessage());
}

} catch (IOException e) {
throw new MetricException("Error al procesar la métrica: " + e.getMessage());
}
}

@Override
public List<String> getAvailableMetrics() {
return new ArrayList<>(AVAILABLE_METRICS);
}

@Override
public RemoteType getRemoteType() {
return RemoteType.GITHUB;
}
}
37 changes: 35 additions & 2 deletions src/main/resources/a4iDefault.json
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,26 @@
"type": "java.lang.Integer",
"description": "Balance de equipos y open issues",
"unit": "ratio"
}
},
{
"name": "reopenedIssuesAvg",
"type": "java.lang.Double",
"description": "Media de issues reabiertos en el ultimo sprint",
"unit": "issues/sprint"
},
{
"name": "reopenedIssuesResolutionRate",
"type": "java.lang.Double",
"description": "Porcentaje de issues que se resuelven en el primer intento sin necesidad de ser reabiertos",
"unit": "ratio"
},

{
"name": "postClosureActivityRate",
"type": "java.lang.Double",
"description": "Porcentaje de issues cerrados que reciben comentarios o actividades adicionales",
"unit": "ratio"
}
],
"indicators": [
{
Expand Down Expand Up @@ -310,6 +329,20 @@
"type": "java.lang.Double",
"description": "Tiempo para arreglos",
"unit": "ratio"
},
{
"name": "calidadResolucion",
"type": "java.lang.Double",
"description": "Indicador que mide la calidad en la resolución de issues considerando su reapertura, resolución inicial y actividad posterior al cierre.",
"unit": "%",
"limits": {
"ok": 90,
"warning": 65,
"critical": 30
}

}
]

]

}
56 changes: 56 additions & 0 deletions src/test/java/us/muit/fs/a4i/model/remote/RemoteEnquirerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
*
*/
package us.muit.fs.a4i.model.remote;

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

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

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.ReportI;
import us.muit.fs.a4i.model.entities.ReportItemI;
import us.muit.fs.a4i.model.remote.RemoteEnquirer;

class RemoteEnquirerTest {

private GitHubRemoteEnquirer enquirer;

@BeforeEach
void setUp() throws MetricException, IOException {
enquirer = new GitHubRemoteEnquirer();
}

@Test
void testBuildReport() {
ReportI report = enquirer.buildReport("MIT-FS/Audit4Improve-API");
assertNotNull(report);
}

@Test
void testGetMetric() throws MetricException {
ReportItemI metric = enquirer.getMetric("reopenedIssuesAvg", "MIT-FS/Audit4Improve-API");
assertNotNull(metric);
}


@Test
void testGetAvailableMetrics() {
List<String> metrics = enquirer.getAvailableMetrics();
assertEquals(3, metrics.size());
assertTrue(metrics.contains("reopenedIssuesAvg"));
assertTrue(metrics.contains("firstTryResolutionRate"));
assertTrue(metrics.contains("postClosureActivityRate"));
}

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