diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml new file mode 100644 index 00000000..7aaf8ebd --- /dev/null +++ b/.github/workflows/pipeline.yml @@ -0,0 +1,34 @@ +name: Demostrador para Acciones GitHub +on: [push] +jobs: + Explore-GitHub-Actions: + runs-on: ubuntu-latest + steps: + - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." + - run: echo "🧠This job is now running on a ${{ runner.os }} server hosted by GitHub!" + - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." + - name: Check out repository code + uses: actions/checkout@v3 + - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." + - run: echo "ðŸ–¥ï¸ The workflow is now ready to test your code on the runner." + - name: List files in the repository + run: | + ls ${{ github.workspace }} + - run: echo "ðŸ This job's status is ${{ job.status }}." + + dump_contexts_to_log: + runs-on: ubuntu-latest + steps: + - name: Mostrando contexto GitHub + id: github_context_step + run: echo '${{ toJSON(github) }}' + - name: Mostrando contexto job + run: echo '${{ toJSON(job) }}' + - name: Mostrando contexto steps + run: echo '${{ toJSON(steps) }}' + - name: Mostrando contexto runner + run: echo '${{ toJSON(runner) }}' + - name: Mostrando contexto strategy + run: echo '${{ toJSON(strategy) }}' + - name: Mostrando contexto matrix + run: echo '${{ toJSON(matrix) }}' diff --git a/.github/workflows/pruebas.yml b/.github/workflows/pruebas.yml new file mode 100644 index 00000000..2db842e3 --- /dev/null +++ b/.github/workflows/pruebas.yml @@ -0,0 +1,28 @@ +name: Flujo de trabajo para ejecutar los test +on: + workflow_dispatch: + push: + branches: [ V.0.2 ] + pull_request: + branches: [ V.0.2 ] + schedule: + - cron: '1 23 * * 0-4' +jobs: + Build: + runs-on: ubuntu-latest + env: + GITHUB_LOGIN: ${{ github.actor }} + GITHUB_PACKAGES: ${{ secrets.GHTOKEN }} + GITHUB_OAUTH: ${{ secrets.GHTOKEN }} + steps: + - name: Clonando el repositorio y estableciendo el espacio de trabajo + uses: actions/checkout@v3 + - name: Configurando java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '16' + - name: Construyendo y probando el código + run: | + chmod +x gradlew + ./gradlew build diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..7b016a89 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.compile.nullAnalysis.mode": "automatic" +} \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/config/Checker.java b/src/main/java/us/muit/fs/a4i/config/Checker.java index 76b4896e..bda1088d 100644 --- a/src/main/java/us/muit/fs/a4i/config/Checker.java +++ b/src/main/java/us/muit/fs/a4i/config/Checker.java @@ -1,240 +1,43 @@ -/** - * - */ package us.muit.fs.a4i.config; -import java.io.BufferedReader; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.HashMap; import java.util.logging.Logger; -import javax.json.*; - /** *

- * Clase para verificar las métricas e indicadores - *

- *

- * La API incluye sus métricas e indicadores por defecto en la carpeta - * resources, en el fichero a4iDefault.json - *

- *

- * Si la aplicación cliente crea nuevas métricas o indicadores las guarda en un - * fichero json de configuración, que tendrá que indicársele al Checker - *

- *

- * Deuda técnica: analizar la posibilidad de leer sólo una vez y mantener en - * memoria + * Esta clase permite acceder a las interfaces para configurar y verificar + * métricas e indicadores Se mantiene separada la lógica de la gestión de + * configuración de métricas e indicadores porque en el futuro van a ser + * bastante diferentes. En la versión actual son muy similares y por tanto el + * diseño no es bueno ya que no identifica bien la reutilización *

* - * @author Isabel Román + * @author Isabel Román * */ public class Checker { private static Logger log = Logger.getLogger(Checker.class.getName()); - private String a4iMetrics = "a4iDefault.json"; - /* - * Definir la ruta completa del fichero de configuración - */ - private String appMetrics = null; - public void setAppMetrics(String appMetricsPath) { - appMetrics = appMetricsPath; + private MetricConfigurationI metricConf; + private IndicatorConfigurationI indiConf; +/** + * El constructor recibe los objetos configuradores, que podrán ser diferentes según el contexto. + */ + Checker(MetricConfigurationI metricConf, IndicatorConfigurationI indiConf) { + this.metricConf = metricConf; + this.indiConf = indiConf; } /** - *

- * Comprueba si la métrica está definida en el fichero por defecto o en el de la - * aplicación cliente - *

- *

- * También verifica que el tipo es el adecuado - *

- * - * @param metricName nombre de la métrica que se quiere comprobar - * @param metricType tipo de la métrica - * @return metricDefinition Si la métrica está definida y el tipo es correcto se devuelve un mapa con las unidades y la descripción - * @throws FileNotFoundException Si no se localiza el fichero de configuración + * @return interfaz para la configuración de métricas */ - public HashMap definedMetric(String metricName, String metricType) throws FileNotFoundException { - log.info("Checker solicitud de búsqueda métrica " + metricName); - - HashMap metricDefinition=null; - - String filePath="/"+a4iMetrics; - log.info("Buscando el archivo " + filePath); - InputStream is=this.getClass().getResourceAsStream(filePath); - log.info("InputStream "+is+" para "+filePath); - InputStreamReader isr = new InputStreamReader(is); - - - metricDefinition = isDefinedMetric(metricName, metricType, isr); - if ((metricDefinition==null) && appMetrics != null) { - is=new FileInputStream(appMetrics); - isr=new InputStreamReader(is); - metricDefinition = isDefinedMetric(metricName, metricType, isr); - } - - return metricDefinition; - } - - private HashMap isDefinedMetric(String metricName, String metricType, InputStreamReader isr) - throws FileNotFoundException { - - HashMap metricDefinition=null; - - - JsonReader reader = Json.createReader(isr); - log.info("Creo el JsonReader"); - - JsonObject confObject = reader.readObject(); - log.info("Leo el objeto"); - reader.close(); - - log.info("Muestro la configuración leída " + confObject); - JsonArray metrics = confObject.getJsonArray("metrics"); - log.info("El número de métricas es " + metrics.size()); - for (int i = 0; i < metrics.size(); i++) { - log.info("nombre: " + metrics.get(i).asJsonObject().getString("name")); - if (metrics.get(i).asJsonObject().getString("name").equals(metricName)) { - log.info("Localizada la métrica"); - log.info("tipo: " + metrics.get(i).asJsonObject().getString("type")); - if (metrics.get(i).asJsonObject().getString("type").equals(metricType)) { - metricDefinition=new HashMap(); - metricDefinition.put("description", metrics.get(i).asJsonObject().getString("description")); - metricDefinition.put("unit", metrics.get(i).asJsonObject().getString("unit")); - } - - } - } - - return metricDefinition; + public MetricConfigurationI getMetricConfiguration() { + return this.metricConf; } /** - *

- * Comprueba si el indicador está definido en el fichero por defecto o en el de - * la aplicación cliente - *

- *

- * También verifica que el tipo es el adecuado - *

- * - * @param indicatorName nombre del indicador que se quiere comprobar - * @param indicatorType tipo del indicador - * @return indicatorDefinition Si el indicador está definido y el tipo es correcto se devuelve un mapa con las unidades y la descripción - * @throws FileNotFoundException Si no se localiza el fichero de configuración + * @return interfaz para la configuración de indicadores */ - public HashMap definedIndicator(String indicatorName, String indicatorType) throws FileNotFoundException { - HashMap indicatorDefinition=null; - log.info("Checker solicitud de búsqueda indicador " + indicatorName); - boolean defined = false; - - - String filePath="/"+a4iMetrics; - log.info("Buscando el archivo " + filePath); - InputStream is=this.getClass().getResourceAsStream(filePath); - log.info("InputStream "+is+" para "+filePath); - InputStreamReader isr = new InputStreamReader(is); - - - - indicatorDefinition = isDefinedIndicator(indicatorName, indicatorType, isr); - if ((indicatorDefinition==null) && appMetrics != null) { - is=new FileInputStream(appMetrics); - isr=new InputStreamReader(is); - indicatorDefinition = isDefinedIndicator(indicatorName, indicatorType, isr); - } - - return indicatorDefinition; - } - - private HashMap isDefinedIndicator(String indicatorName, String indicatorType, InputStreamReader isr) - throws FileNotFoundException { - HashMap indicatorDefinition=null; - - JsonReader reader = Json.createReader(isr); - log.info("Creo el JsonReader"); - - JsonObject confObject = reader.readObject(); - log.info("Leo el objeto"); - reader.close(); - - log.info("Muestro la configuración leída " + confObject); - JsonArray indicators = confObject.getJsonArray("indicators"); - log.info("El número de indicadores es " + indicators.size()); - for (int i = 0; i < indicators.size(); i++) { - log.info("nombre: " + indicators.get(i).asJsonObject().getString("name")); - if (indicators.get(i).asJsonObject().getString("name").equals(indicatorName)) { - log.info("Localizado el indicador"); - log.info("tipo: " + indicators.get(i).asJsonObject().getString("type")); - if (indicators.get(i).asJsonObject().getString("type").equals(indicatorType)) { - indicatorDefinition=new HashMap(); - indicatorDefinition.put("description", indicators.get(i).asJsonObject().getString("description")); - indicatorDefinition.put("unit", indicators.get(i).asJsonObject().getString("unit")); - } - - } - } - - return indicatorDefinition; + public IndicatorConfigurationI getIndicatorConfiguration() { + return this.indiConf; } - - public HashMap getMetricInfo(String metricName) throws FileNotFoundException { - log.info("Checker solicitud de búsqueda detalles de la métrica " + metricName); - - HashMap metricDefinition=null; - String filePath="/"+a4iMetrics; - log.info("Buscando el archivo " + filePath); - InputStream is=this.getClass().getResourceAsStream(filePath); - log.info("InputStream "+is+" para "+filePath); - InputStreamReader isr = new InputStreamReader(is); - - log.info("Creo el inputStream"); - metricDefinition = getMetricInfo(metricName, isr); - if ((metricDefinition==null) && appMetrics != null) { - is=new FileInputStream(appMetrics); - isr=new InputStreamReader(is); - metricDefinition = getMetricInfo(metricName, isr); - } - - return metricDefinition; - } - - - private HashMap getMetricInfo(String metricName, InputStreamReader isr) - throws FileNotFoundException { - - HashMap metricDefinition=null; - - JsonReader reader = Json.createReader(isr); - log.info("Creo el JsonReader"); - - JsonObject confObject = reader.readObject(); - log.info("Leo el objeto"); - reader.close(); - - log.info("Muestro la configuración leída " + confObject); - JsonArray metrics = confObject.getJsonArray("metrics"); - log.info("El número de métricas es " + metrics.size()); - for (int i = 0; i < metrics.size(); i++) { - log.info("nombre: " + metrics.get(i).asJsonObject().getString("name")); - if (metrics.get(i).asJsonObject().getString("name").equals(metricName)) { - log.info("Localizada la métrica"); - log.info("tipo: " + metrics.get(i).asJsonObject().getString("type")); - metricDefinition=new HashMap(); - metricDefinition.put("name", metrics.get(i).asJsonObject().getString("name")); - metricDefinition.put("description", metrics.get(i).asJsonObject().getString("description")); - metricDefinition.put("unit", metrics.get(i).asJsonObject().getString("unit")); - metricDefinition.put("type", metrics.get(i).asJsonObject().getString("type")); - - } - } - - return metricDefinition; - } - } diff --git a/src/main/java/us/muit/fs/a4i/config/Context.java b/src/main/java/us/muit/fs/a4i/config/Context.java index db1a1b18..d304c92f 100644 --- a/src/main/java/us/muit/fs/a4i/config/Context.java +++ b/src/main/java/us/muit/fs/a4i/config/Context.java @@ -3,47 +3,59 @@ */ package us.muit.fs.a4i.config; +import java.awt.Color; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.util.Properties; import java.util.Set; import java.util.logging.Logger; -import java.awt.Font; -import us.muit.fs.a4i.model.entities.Indicator; -import us.muit.fs.a4i.model.entities.Metric; +import us.muit.fs.a4i.model.entities.IndicatorI; +import us.muit.fs.a4i.model.entities.Font; /** *

- * Clase para la gestión de los parámetros de contexto + * Clase para la gestión de los parámetros de contexto *

*

- * El objetivo de Context es el manejo de la configuración + * El objetivo de Context es el manejo de la configuración de la api. La + * configuración por defecto se separa en dos ficheros principales: + *

    + *
  1. a4i.conf: contiene la configuración por defecto de la api. Permite + * seleccionar el tipo de remoto con el que se quiere interaccionar, el tipo de + * persistencia de los informes y características de presentación de estos + * informes.
  2. + *
  3. a4iDefault.json: contiene la configuración por defecto de métricas e + * indicadores. Esta configuración se maneja en la clase checker
  4. + *
+ * Hay una configuración embebida en el jar, es decir, una configuración por + * defecto. Pero esta puede ser modificada si la aplicación cliente define + * ficheros de configuración personalizados. *

*

- * En el estado actual Contexto sólo es una aproximación a las posiblidades de - * configuración. Se presentan posibilidades para: + * En el estado actual Context es una aproximación a las posiblidades de + * configuración. Se presentan posibilidades para: *

*
    - *
  • Localizar el fichero en la carpeta resources, incluida en - * el jar
  • - *
  • Localizar el fichero en el home de usuario
  • - *
  • Localizar el fichero en una ruta introducida de forma - * "programada"
  • + *
  • Localizar el fichero a4i.conf en la carpeta resources, incluida en el + * jar
  • + *
  • Localizar el fichero *.conf (configuración personalizada) en el home de + * usuario
  • + *
  • Localizar el fichero *.conf (configuración personalizada) en una ruta + * introducida de forma "programada"
  • *
*

- * Único punto para acceso a variables que pueden ser leídas por cualquiera, - * configuradas sólo por la clase context + * Único punto para acceso a variables que pueden ser leídas por cualquiera, + * configuradas sólo por la clase context *

*

- * Sigue el patrón singleton + * Sigue el patrón singleton. Context tiene la responsabilidad de crear Checker, es decir, debe seleccionar el IndicatorConfiguration y el MetricConfiguration adecuados al contexto. + * Se crean la primera vez que se solicita Context, por lo que los ficheros de configuración no se pueden cambiar "en caliente", sólo al inicio de la ejecución *

* - * @author Isabel Román + * @author Isabel Román * */ public class Context { @@ -51,67 +63,151 @@ public class Context { private static Logger log = Logger.getLogger(Context.class.getName()); private static Context contextInstance = null; + /** + * Propiedades de la API Se leerán del fichero de configuración por defecto y + * del especificado por la aplicación, si lo hubiera + */ private Properties properties = null; - // Fichero de propiedades de la API, embebido en el jar + /** + * Fichero de propiedades de configuración de la API, embebido en el jar + */ private static String confFile = "a4i.conf"; - // Fichero de propiedades de la API establecido por la aplicación cliente - private static String appConFile = null; - // Fichero de configuración de métricas e indicadores por defecto, embebido en - // el jar + + /** + * Fichero de especificación de métricas e indicadores por defecto, embebido en + * el jar + * + */ private static String defaultFile = "a4iDefault.json"; - // Fichero de configuración de métricas e indicadores establecido por la - // aplicación cliente + + /** + * Fichero de propiedades de la API establecido por la aplicación cliente + */ + private static String appConfFile = null; + + /** + * Fichero de especificación de métricas e indicadores establecido por la + * aplicación cliente + */ private static String appFile = null; + /** + * Referencia al verificador de métricas e indicadores + */ private Checker checker = null; - private Context() throws IOException { - setProperties(); - checker = new Checker(); + /** + *

+ * Constructor privado, sigue el patrón singleton. El único objeto posible se + * crea al invocar el método getContext + *

+ * + * @throws IOException + */ + private Context(Checker checker) throws IOException { + setProperties(); + this.checker = checker; + log.info("Propiedades del contexto establecidas"); + } + + /** + *

+ * Establece la ruta del fichero de métricas e indicadores indicado por el + * cliente/aplicación + *

+ * + * @param filename ruta al fichero de configuración de métricas e indicadores de + * la aplicación cliente + */ + public static void setAppRI(String filename) { + log.fine("Fichero configuración de métricas establecido "+filename); + appFile = filename; + } + + /** + *

+ * Consulta la ruta del fichero de configuración de métricas e indicadores del + * cliente/aplicación + *

+ * + * @return ruta del fichero de configuración de métricas e indicadores de la + * aplicación cliente + */ + public static String getAppRI() { + return appFile; + } + + /** + * @return la ruta al fichero de configuración de indicadores y métricas por + * defecto + */ + public static String getDefaultRI() { + return defaultFile; } /** *

- * Devuelve la instancia única de Context. Si no estaba creada la crea, leyendo - * la configuración por defecto + * Devuelve la instancia única de Context. Si no estaba creada la crea, leyendo + * la configuración por defecto *

* - * @return La instancia única de Context + * @return La instancia única de Context * @throws IOException Si hay problemas con la lectura del fichero de - * configuración + * configuración */ public static Context getContext() throws IOException { /** - * Si no está creada crea la instancia única con las propiedades por defecto + * Si no está creada crea la instancia única con las propiedades por defecto */ if (contextInstance == null) { - contextInstance = new Context(); + Checker checker=createChecker(); + contextInstance = new Context(checker); } return contextInstance; } + /** + * Método responsable de seleccionar el IndicatorConfiguration y el MetricConfiguration adecuado al contexto + * DEUDA TÉCNICA: + * En esta versión al crear el checker no hay ninguna comprobación de contexto, pero en el futuro deberá primero examinarse las propiedas de configuración para decidir que configuradores hay que crear. + * @return + */ + static private Checker createChecker(){ + IndicatorConfigurationI indConf=new IndicatorConfiguration(Context.getDefaultRI(),Context.getAppRI()); + MetricConfigurationI metConf=new MetricConfiguration(Context.getDefaultRI(),Context.getAppRI()); + log.info("Creados los configuradores de métricas e indicadores, se crea checker"); + return new Checker(metConf,indConf); + } /** *

- * Establece el fichero de configuración específico de la aplicación cliente. - * Las propiedades no establecidas se cogerán de la configuración por defecto + * Establece el fichero de configuración específico de la aplicación cliente. + * Las propiedades no establecidas se cogerán de la configuración por defecto *

* - * @param appConPath Ruta completa al fichero de configuración establecido por la - * propiedad cliente + * @param appConPath Ruta completa al fichero de configuración establecido por + * la propiedad cliente * @throws IOException Problema lectura fichero */ public static void setAppConf(String appConPath) throws IOException { /** - * Vuelve a leer las propiedades incluyendo las establecidas por la aplicación + * Vuelve a leer las propiedades incluyendo las establecidas por la aplicación */ - appConFile = appConPath; - + appConfFile = appConPath; + log.fine("Fichero configuración de la API establecido "+appConPath); // customFile=System.getenv("APP_HOME")+customFile; - // Otra opción, Usar una variable de entorno para la localizar la ruta de - // instalación y de ahí coger el fichero de configuración - // También podría localizarse en el home de usuario + // Otra opción, Usar una variable de entorno para localizar la ruta de + // instalación y de ahí coger el fichero de configuración + // También podría localizarse en el home de usuario getContext().properties.load(new FileInputStream(appConPath)); + log.info("Las nuevas propiedades son " + getContext().properties); + } + + public static String getAppConf() throws IOException { + return appConfFile; } + /** + * @return devuelve el verificador (checker) + */ public Checker getChecker() { return checker; } @@ -121,7 +217,7 @@ public Checker getChecker() { * Consulta el tipo de persistencia que se quiere utilizar *

* - * @return El tipo de persistencia usado (NOTA: deuda técnica, podría convenir + * @return El tipo de persistencia usado (NOTA: deuda técnica, podría convenir * usar un enumerado, para controlar mejor los tipos disponibles) * @throws IOException si hay problemas al consultar las propiedades */ @@ -134,82 +230,152 @@ public String getPersistenceType() throws IOException { * Consulta el tipo de remoto que se quiere manejar *

* - * @return El tipo de remoto (NOTA: deuda técnica, podría convenir usar un + * @return El tipo de remoto (NOTA: deuda técnica, podría convenir usar un * enumerado, para controlar mejor los tipos disponibles) * @throws IOException si hay problemas al consultar las propiedades */ public String getRemoteType() throws IOException { + log.info("Se solicita el tipo de remoto"); return properties.getProperty("remote.type"); } /** *

- * No Implementado - * Deberá leer las propiedades adecuadas, como color, tamaño, tipo... y - * construir un objeto Font - * Si no se ha establecido un valor por defecto se crea una fuente simple + * Lee las propiedades adecuadas, como color, tamaño, tipo... y construir un + * objeto Font Si no se ha establecido un valor por defecto se crea una fuente + * simple *

* - * @return La fuente por defecto para indicadores y métricas + * @return La fuente por defecto para indicadores y métricas */ public Font getDefaultFont() { - Font font = null; - // TO DO - String color = properties.getProperty("Font.default.color"); - String height = properties.getProperty("Font.default.height"); - String type = properties.getProperty("Font.default.type"); - return font; + // OJO el color no forma parte de la clase font, por lo que ese atributo debe + // estar fuera + // Podría incluir un parámetro font para devolverlo a la salida y que lo que + // devuelva sea un String con el color + log.info("Busca la información de configuración de la fuente, por defecto"); + + String color = getDefaultParam("color"); + String height = getDefaultParam("height"); + String type = getDefaultParam("type"); + + log.info("Los datos son, color: " + color + " height: " + height + " type: " + type); + log.info("Intento crear la fuente"); + + return new Font(type, Integer.valueOf(height), color); + + } + + private String getDefaultParam(String param) { + String property = "Font.default." + param; + String value = properties.getProperty(property); + /** + * Se asegura de que se da un valor por defecto, aunque no esté configurado en + * el fichero + */ + if (value == null) { + switch (param) { + case "type": + value = "Arial"; + break; + case "color": + value = "black"; + break; + case "height": + value = "12"; + break; + } + } + return value; } /** *

- * No Implementado - *

- *

- * Deberá leer las propiedades adecuadas, como color, tamaño, tipo... y - * construir un objeto Font + * Lee las propiedades adecuadas, como color, tamaño, tipo... y construye un + * objeto Font para la fuente de las métricas *

*

- * Si no se ha definido una fuente para las métricas se debe devolver la fuente + * Si no se ha definido una fuente para las métricas se debe devolver la fuente * por defecto *

* - * @return la fuente para las métricas + * @return la fuente para las métricas */ - public static Font getMetricFont() { - Font font = null; - // TO DO - return font; + public Font getMetricFont() { + log.info("Busca la información de configuración de la fuente, para las métricas"); + + String type = properties.getProperty("Font.metric.type"); + String height = properties.getProperty("Font.metric.height"); + String color = properties.getProperty("Font.metric.color"); + + if (type == null) { + type = getDefaultParam("type"); + log.info("El tipo de la fuente de las metricas es el valor por defecto"); + } + if (height == null) { + height = getDefaultParam("height"); + log.info("El tamaño de la fuente de las metricas es el valor por defecto"); + } + if (color == null) { + color = getDefaultParam("color"); + log.info("El color de la fuente de las metricas es el valor por defecto"); + } + + log.info("Llamo a newFont con los datos color: " + color + " height: " + height + " type: " + type); + + return new Font(type, Integer.valueOf(height), color); + } /** *

- * No Implementado - *

- *

- * Deberá leer las propiedades adecuadas, como color, tamaño, tipo... y - * construir un objeto Font + * Deberá leer las propiedades adecuadas, como color, tamaño, tipo... y + * construir un objeto Font para la fuente del indicador en dicho estado *

* * @param state Estado para el que se solicita el color de fuente - * @return La fuente para el indicador cuando el estado es el parámetro pasado + * @return La fuente para el indicador cuando el estado es el parametro pasado * @throws IOException problema al leer el fichero */ - public static Font getIndicatorFont(Indicator.State state) throws IOException { + public Font getIndicatorFont(IndicatorI.IndicatorState state) throws IOException { + /** + * He eliminado el static, así si funciona Hay que comprobar si el static estaba + * puesto con sentido desde un primer momento + **/ Font font = null; + // TODO: + String propertyState = "Font." + state.toString(); + log.info("Raiz que uso para buscar los datos del indicador en estado " + state + " " + propertyState); + + String color = properties.getProperty(propertyState + ".color"); + String height = properties.getProperty(propertyState + ".height"); + String type = properties.getProperty(propertyState + ".type"); + + log.info("Los datos son, color: " + color + " height: " + height + " type: " + type); + log.info("Intento crear la fuente"); - // TO DO + if (color == null) { + color = properties.getProperty("Font.default.color"); + } + if (height == null) { + height = properties.getProperty("Font.default.height"); + } + if (type == null) { + type = properties.getProperty("Font.default.type"); + } + + font = new Font(type, Integer.valueOf(height), color); return font; } /** *

- * Consulta el nombre de todas las propiedades leídas + * Consulta el nombre de todas las propiedades leídas *

* - * @return Conjunto con todos los nombres de las propiedades de configuración - * leídas + * @return Conjunto con todos los nombres de las propiedades de configuración + * leídas * @throws IOException si hay problemas al leer las propiedades */ public Set getPropertiesNames() throws IOException { @@ -220,23 +386,23 @@ public Set getPropertiesNames() throws IOException { /** *

* Crea las propiedades, incluye las propiedades por defecto, leyendo del - * fichero de conf de la API (configuración por defecto) + * fichero de conf de la API (configuración por defecto) *

* * @throws IOException si hay problemas leyendo el fichero */ private void setProperties() throws IOException { - log.info("Lectura del fichero de configuración por defecto"); + log.info("Lectura del fichero de configuración por defecto"); FileInputStream file; - // Establecemos las propiedades por defecto, del fichero de configuración + // Establecemos las propiedades por defecto, del fichero de configuración // embebido en el jar - + properties = new Properties(); - String filePath="/"+confFile; - InputStream is=this.getClass().getResourceAsStream(filePath); - log.info("InputStream "+is+" para "+filePath); - properties.load(is); - log.fine("Listado de propiedades "+properties); + String filePath = "/" + confFile; + InputStream is = this.getClass().getResourceAsStream(filePath); + log.info("InputStream " + is + " para " + filePath); + properties.load(is); + log.info("Listado de propiedades " + properties); } diff --git a/src/main/java/us/muit/fs/a4i/config/GitFlow.java b/src/main/java/us/muit/fs/a4i/config/GitFlow.java new file mode 100644 index 00000000..1e87ee07 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/config/GitFlow.java @@ -0,0 +1,61 @@ +/** + * + */ +package us.muit.fs.a4i.config; + +/** + * Clase incluida por el equipo 1 del curso 23/24 para apoyo a las métricas de + * conformidad con convenciones de la organización + */ + +public enum GitFlow { + // MARK: - Enum cases + /** The main branch */ + MAIN, + /** The develop branch */ + DEVELOP, + /** A feature branch */ + FEATURE, + /** A release branch */ + RELEASE, + /** A hotfix branch */ + HOTFIX; + + // MARK: - Methods + @Override + /** + * Returns the string representation of the enum case + */ + public String toString() { + return switch (this) { + case MAIN -> "main"; + case DEVELOP -> "develop"; + case FEATURE -> "feature"; + case RELEASE -> "release"; + case HOTFIX -> "hotfix"; + }; + } + + /** + * Checks if the branch name is a valid Git Flow branch + * + * @param branchName the name of the branch + * @return true if the branch name is a valid Git Flow branch + */ + public static boolean isGitFlowBranch(String branchName) { + return branchName.equals(MAIN.toString()) || branchName.equals(DEVELOP.toString()) + || branchName.matches(branchNamePattern(FEATURE.toString())) + || branchName.matches(branchNamePattern(RELEASE.toString())) + || branchName.matches(branchNamePattern(HOTFIX.toString())); + } + + /** + * Returns the pattern for the branch name + * + * @param branchType the type of branch + * @return the regex pattern for the branch name + */ + private static String branchNamePattern(String branchType) { + return "^" + branchType + "/[a-zA-Z0-9_-]+$"; + } +} \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/config/IndicatorConfiguration.java b/src/main/java/us/muit/fs/a4i/config/IndicatorConfiguration.java new file mode 100644 index 00000000..75b54f30 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/config/IndicatorConfiguration.java @@ -0,0 +1,243 @@ +/** + * + */ +package us.muit.fs.a4i.config; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Logger; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonReader; + +import us.muit.fs.a4i.model.entities.IndicatorI; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; +import us.muit.fs.a4i.model.entities.ReportItemI; + +/** + * @author Isabel Román + * + */ +public class IndicatorConfiguration implements IndicatorConfigurationI { + /** + * El creador de IndicatorConfiguration tiene que indicar los nombres de los ficheros de métricas por defecto y del cliente + * @param defaultRI nombre del fichero de métricas por defecto + * @param appRI nombre del fichero de métricas definido por el cliente + */ + public IndicatorConfiguration(String defaultRI, String appRI) { + super(); + this.defaultRI = defaultRI; + this.appRI = appRI; + } + + private static Logger log = Logger.getLogger(Checker.class.getName()); + + private String CRITICAL_LIMIT = "limits.critical"; + private String WARNING_LIMIT = "limits.warning"; + private String OK_LIMIT = "limits.ok"; + private String defaultRI; + private String appRI; + + @Override + /** + *

+ * Comprueba si el indicador está definido en el fichero por defecto o en el de + * la aplicación cliente + *

+ *

+ * También verifica que el tipo es el adecuado + *

+ * + * @param indicatorName nombre del indicador que se quiere comprobar + * @param indicatorType tipo del indicador + * @return indicatorDefinition Si el indicador está definido y el tipo es + * correcto se devuelve un mapa con las unidades y la descripción + * @throws FileNotFoundException Si no se localiza el fichero de configuración + */ + public HashMap definedIndicator(String name, String type) throws FileNotFoundException { + HashMap indicatorDefinition = null; + log.info("Checker solicitud de búsqueda indicador " + name); + + String filePath = "/" + defaultRI; + log.info("Buscando el archivo " + filePath); + InputStream is = this.getClass().getResourceAsStream(filePath); + log.info("InputStream " + is + " para " + filePath); + InputStreamReader isr = new InputStreamReader(is); + + indicatorDefinition = isDefinedIndicator(name, type, isr); + if ((indicatorDefinition == null) && appRI != null) { + is = new FileInputStream(appRI); + isr = new InputStreamReader(is); + indicatorDefinition = isDefinedIndicator(name, type, isr); + } + + return indicatorDefinition; + } + + /** + * Busca el indicador con un nombre y un tipo determinado y devuelve sus + * características en un mapa + * + * @param indicatorName + * @param indicatorType + * @param isr + * @return + * @throws FileNotFoundException + */ + private HashMap isDefinedIndicator(String indicatorName, String indicatorType, + InputStreamReader isr) throws FileNotFoundException { + HashMap indicatorDefinition = null; + + JsonReader reader = Json.createReader(isr); + log.info("Creo el JsonReader"); + + JsonObject confObject = reader.readObject(); + log.info("Leo el objeto"); + reader.close(); + + log.info("Muestro la configuración leída " + confObject); + JsonArray indicators = confObject.getJsonArray("indicators"); + log.info("El número de indicadores es " + indicators.size()); + for (int i = 0; i < indicators.size(); i++) { + log.info("nombre: " + indicators.get(i).asJsonObject().getString("name")); + if (indicators.get(i).asJsonObject().getString("name").equals(indicatorName)) { + log.info("Localizado el indicador"); + log.info("tipo: " + indicators.get(i).asJsonObject().getString("type")); + if (indicators.get(i).asJsonObject().getString("type").equals(indicatorType)) { + indicatorDefinition = new HashMap(); + indicatorDefinition.put("name", indicatorName); + indicatorDefinition.put("type", indicatorType); + indicatorDefinition.put("description", indicators.get(i).asJsonObject().getString("description")); + indicatorDefinition.put("unit", indicators.get(i).asJsonObject().getString("unit")); + + JsonObject limits = indicators.get(i).asJsonObject().getJsonObject("limits"); + int okLimit = 0; + int warningLimit = 0; + int criticalLimit = 0; + + if (limits != null) { + okLimit = limits.getInt("ok"); + warningLimit = limits.getInt("warning"); + criticalLimit = limits.getInt("critical"); + indicatorDefinition.put(OK_LIMIT, Integer.toString(okLimit)); + indicatorDefinition.put(WARNING_LIMIT, Integer.toString(warningLimit)); + indicatorDefinition.put(CRITICAL_LIMIT, Integer.toString(criticalLimit)); + } else { + log.info("El fichero de configuración no especifica límites para este indicador"); + } + + } + + } + } + + return indicatorDefinition; + } + + @Override + public List listAllIndicators() throws FileNotFoundException { + log.info("Consulta todas las métricas"); + + List allmetrics = new ArrayList(); + + String filePath = "/" + defaultRI; + log.info("Buscando el archivo " + filePath); + InputStream is = this.getClass().getResourceAsStream(filePath); + log.info("InputStream " + is + " para " + filePath); + InputStreamReader isr = new InputStreamReader(is); + + JsonReader reader = Json.createReader(isr); + log.info("Creo el JsonReader"); + + JsonObject confObject = reader.readObject(); + log.info("Leo el objeto"); + reader.close(); + + log.info("Muestro la configuración leída " + confObject); + JsonArray metrics = confObject.getJsonArray("indicators"); + log.info("El número de indicadores es " + metrics.size()); + for (int i = 0; i < metrics.size(); i++) { + log.info("Añado nombre: " + metrics.get(i).asJsonObject().getString("name")); + allmetrics.add(metrics.get(i).asJsonObject().getString("name")); + } + if (appRI != null) { + is = new FileInputStream(appRI); + isr = new InputStreamReader(is); + reader = Json.createReader(isr); + confObject = reader.readObject(); + reader.close(); + + log.info("Muestro la configuración leída " + confObject); + metrics = confObject.getJsonArray("metrics"); + log.info("El número de métricas es " + metrics.size()); + for (int i = 0; i < metrics.size(); i++) { + log.info("Añado nombre: " + metrics.get(i).asJsonObject().getString("name")); + allmetrics.add(metrics.get(i).asJsonObject().getString("name")); + } + } + + return allmetrics; + } + + @Override + public IndicatorState getIndicatorState(ReportItemI indicator) { + // TODO: change indicator definitions key name to a constant. + + // Necesita optimización del cálculo de estado + + String indicatorType = indicator.getValue().getClass().getName(); + + IndicatorState finalState = IndicatorState.UNDEFINED; + try { + HashMap indicatorDefinition = definedIndicator(indicator.getName(), indicatorType); + + String criticalLimit = indicatorDefinition.get(CRITICAL_LIMIT); + String warningLimit = indicatorDefinition.get(WARNING_LIMIT); + String okLimit = indicatorDefinition.get(OK_LIMIT); + + // Si no se han encontrado límites definidos para ese indicador el estado es + // UNDEFINED. + if (criticalLimit != null && warningLimit != null && okLimit != null) { + // Se tienen en cuenta los posibles tipos de indicadores para compararlos. + if (indicatorType.equals(Integer.class.getName())) { + Integer value = (Integer) indicator.getValue(); + + if (value >= Integer.parseInt(criticalLimit)) + finalState = IndicatorState.CRITICAL; + else if (value <= Integer.parseInt(okLimit)) + finalState = IndicatorState.OK; + else + finalState = IndicatorState.WARNING; + + } else if (indicatorType.equals(Double.class.getName())) { + Double value = (Double) indicator.getValue(); + + if (value >= Integer.parseInt(criticalLimit)) + finalState = IndicatorState.CRITICAL; + else if (value <= Integer.parseInt(okLimit)) + finalState = IndicatorState.OK; + else + finalState = IndicatorState.WARNING; + + } + } else { + log.warning("No se han encontrado límites definidos para el indicador: " + indicator.getName()); + finalState = IndicatorState.UNDEFINED; + } + + } catch (Exception e) { + + e.printStackTrace(); + } + log.info("El estado del Indicador " + indicator.getName() + " es " + finalState.toString()); + return finalState; + } + +} diff --git a/src/main/java/us/muit/fs/a4i/config/IndicatorConfigurationI.java b/src/main/java/us/muit/fs/a4i/config/IndicatorConfigurationI.java new file mode 100644 index 00000000..f8dbf25a --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/config/IndicatorConfigurationI.java @@ -0,0 +1,48 @@ +/** + * + */ +package us.muit.fs.a4i.config; + +import java.io.FileNotFoundException; +import java.util.HashMap; +import java.util.List; + +import us.muit.fs.a4i.model.entities.IndicatorI; +import us.muit.fs.a4i.model.entities.ReportItemI; + +/** + * @author Isabel Román + * + */ +public interface IndicatorConfigurationI { + /** + *

+ * Verifica si el indicador existe y el tipo es correcto + *

+ * + * @param name nombre del indicador + * @param type tipo del valor del indicador + * @return mapa con los detalles del indicador + * @throws FileNotFoundException si hay algún problema con el fichero de + * configuración + */ + public HashMap definedIndicator(String name, String type) throws FileNotFoundException; + + /** + * @return lista con los nombres de todos los indicadores disponibles + * @throws FileNotFoundException en caso de problemas con el fichero de + * configuración + */ + public List listAllIndicators() throws FileNotFoundException; + + /** + *

+ * Infiere el estado del indicador a partir del valor del ReportItem y de la + * configuración de estados en el fichero + *

+ * + * @param indicator ReportItem para el que se quiere calcular el estado + * @return El estado del indicador + */ + public IndicatorI.IndicatorState getIndicatorState(ReportItemI indicator); +} diff --git a/src/main/java/us/muit/fs/a4i/config/MetricConfiguration.java b/src/main/java/us/muit/fs/a4i/config/MetricConfiguration.java new file mode 100644 index 00000000..c7521f58 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/config/MetricConfiguration.java @@ -0,0 +1,256 @@ +package us.muit.fs.a4i.config; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Logger; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonReader; + +public class MetricConfiguration implements MetricConfigurationI { + + /** + * El creador de MetricConfiguration tiene que indicar los nombres de los + * ficheros de métricas por defecto y del cliente + * + * @param defaultRI nombre del fichero de métricas por defecto + * @param appRI nombre del fichero de métricas definido por el cliente + */ + public MetricConfiguration(String defaultRI, String appRI) { + super(); + this.defaultRI = defaultRI; + this.appRI = appRI; + } + + private static Logger log = Logger.getLogger(Checker.class.getName()); + private String defaultRI; + private String appRI; + + /** + * Método privado que verifica si una métrica está definida en un fichero de + * configuración determinado y si efectivamente tiene el tipo esperado + * + * @param metricName String con el nombre de la métrica a buscar + * @param metricType String con el tipo de la métrica + * @param isr InputStreamReader del fichero de configuración + * @return Mapa de parámetros de la métrica, siempre que exista y sea del tipo + * especificado + * + */ + private HashMap isDefinedMetric(String metricName, String metricType, InputStreamReader isr) { + + HashMap metricDefinition = null; + + JsonReader reader = Json.createReader(isr); + log.info("Creo el JsonReader"); + + JsonObject confObject = reader.readObject(); + log.info("Leo el objeto"); + reader.close(); + + log.info("Muestro la configuración leída " + confObject); + JsonArray metrics = confObject.getJsonArray("metrics"); + log.info("El número de métricas es " + metrics.size()); + for (int i = 0; i < metrics.size(); i++) { + log.info("nombre: " + metrics.get(i).asJsonObject().getString("name")); + if (metrics.get(i).asJsonObject().getString("name").equals(metricName)) { + log.info("Localizada la métrica"); + log.info("tipo: " + metrics.get(i).asJsonObject().getString("type")); + if (metrics.get(i).asJsonObject().getString("type").equals(metricType)) { + metricDefinition = new HashMap(); + metricDefinition.put("name", metricName); + metricDefinition.put("type", metricType); + metricDefinition.put("description", metrics.get(i).asJsonObject().getString("description")); + metricDefinition.put("unit", metrics.get(i).asJsonObject().getString("unit")); + } + + } + } + + return metricDefinition; + } + + /** + * Obtiene los datos de una métrica en un fichero de configuración + * + * @param metricName String con el nombre de la métrica buscada + * @param isr InputStreamReader del fichero de configuración + * @return Mapa con las características de la métrica + * + */ + private HashMap getMetric(String metricName, InputStreamReader isr) { + + HashMap metricDefinition = null; + + JsonReader reader = Json.createReader(isr); + log.info("Creo el JsonReader"); + + JsonObject confObject = reader.readObject(); + log.info("Leo el objeto"); + reader.close(); + + log.info("Muestro la configuración leída " + confObject); + JsonArray metrics = confObject.getJsonArray("metrics"); + log.info("El número de métricas es " + metrics.size()); + for (int i = 0; i < metrics.size(); i++) { + log.info("nombre: " + metrics.get(i).asJsonObject().getString("name")); + if (metrics.get(i).asJsonObject().getString("name").equals(metricName)) { + log.info("Localizada la métrica"); + metricDefinition = new HashMap(); + metricDefinition.put("name", metricName); + metricDefinition.put("type", metrics.get(i).asJsonObject().getString("type")); + metricDefinition.put("description", metrics.get(i).asJsonObject().getString("description")); + metricDefinition.put("unit", metrics.get(i).asJsonObject().getString("unit")); + } + } + + return metricDefinition; + } + + @Override + public HashMap definedMetric(String name, String type) throws FileNotFoundException { + log.info("Checker solicitud de búsqueda métrica " + name); + + HashMap metricDefinition = null; + InputStream is = null; + InputStreamReader isr = null; + String filePath = "/" + defaultRI; + try { + + log.info("Buscando el archivo " + filePath); + is = this.getClass().getResourceAsStream(filePath); + log.info("InputStream " + is + " para " + filePath); + isr = new InputStreamReader(is); + + /** + * Busca primero en el fichero de configuración de métricas por defecto + */ + metricDefinition = isDefinedMetric(name, type, isr); + } catch (NullPointerException e) { + throw new FileNotFoundException( + "No se localiza el fichero de la configuración por defecto de la API " + filePath); + } + try { + /** + * En caso de que no estuviera ahí la métrica busco en el fichero de + * configuración de la aplicación + */ + if ((metricDefinition == null) && appRI != null) { + is = new FileInputStream(appRI); + isr = new InputStreamReader(is); + metricDefinition = isDefinedMetric(name, type, isr); + } + } catch (NullPointerException e) { + throw new FileNotFoundException("No se localiza el fichero de la aplicación cliente " + appRI); + } + + return metricDefinition; + } + + @Override + public HashMap getMetricInfo(String name) throws FileNotFoundException { + log.info("Consulta información de la métrica " + name); + HashMap metricDefinition = null; + String filePath = "/" + defaultRI; + InputStream is = null; + InputStreamReader isr = null; + try { + + log.info("Buscando el archivo " + filePath); + + is = this.getClass().getResourceAsStream(filePath); + log.info("InputStream " + is + " para " + filePath); + isr = new InputStreamReader(is); + + /** + * Busca primero en el fichero de configuración de métricas por defecto + */ + metricDefinition = getMetric(name, isr); + } catch (NullPointerException e) { + throw new FileNotFoundException( + "No se localiza el fichero de la configuración por defecto de la API " + filePath); + } + try { + /** + * En caso de que no estuviera ahí la métrica busco en el fichero de + * configuración de la aplicación + */ + if ((metricDefinition == null) && appRI != null) { + is = new FileInputStream(appRI); + isr = new InputStreamReader(is); + metricDefinition = getMetric(name, isr); + } + } catch (NullPointerException e) { + throw new FileNotFoundException("No se localiza el fichero de la aplicación cliente " + appRI); + } + + return metricDefinition; + } + + @Override + public List listAllMetrics() throws FileNotFoundException { + log.info("Consulta todas las métricas"); + + List allmetrics = new ArrayList(); + InputStream is = null; + InputStreamReader isr = null; + JsonReader reader = null; + JsonObject confObject = null; + JsonArray metrics = null; + + String filePath = "/" + defaultRI; + try { + log.info("Buscando el archivo " + filePath); + is = this.getClass().getResourceAsStream(filePath); + log.info("InputStream " + is + " para " + filePath); + isr = new InputStreamReader(is); + + reader = Json.createReader(isr); + log.info("Creo el JsonReader"); + + confObject = reader.readObject(); + log.info("Leo el objeto"); + reader.close(); + + log.info("Muestro la configuración leída " + confObject); + metrics = confObject.getJsonArray("metrics"); + log.info("El número de métricas es " + metrics.size()); + for (int i = 0; i < metrics.size(); i++) { + log.info("Añado nombre: " + metrics.get(i).asJsonObject().getString("name")); + allmetrics.add(metrics.get(i).asJsonObject().getString("name")); + } + } catch (NullPointerException e) { + throw new FileNotFoundException( + "No se localiza el fichero de la configuración por defecto de la API " + filePath); + } + + try { + if (appRI != null) { + is = new FileInputStream(appRI); + isr = new InputStreamReader(is); + reader = Json.createReader(isr); + confObject = reader.readObject(); + reader.close(); + + log.info("Muestro la configuración leída " + confObject); + metrics = confObject.getJsonArray("metrics"); + log.info("El número de métricas es " + metrics.size()); + for (int i = 0; i < metrics.size(); i++) { + log.info("Añado nombre: " + metrics.get(i).asJsonObject().getString("name")); + allmetrics.add(metrics.get(i).asJsonObject().getString("name")); + } + } + } catch (NullPointerException e) { + throw new FileNotFoundException("No se localiza el fichero de la aplicación cliente " + appRI); + } + + return allmetrics; + } +} \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/config/MetricConfigurationI.java b/src/main/java/us/muit/fs/a4i/config/MetricConfigurationI.java new file mode 100644 index 00000000..b0a7dfc9 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/config/MetricConfigurationI.java @@ -0,0 +1,45 @@ +package us.muit.fs.a4i.config; + +import java.io.FileNotFoundException; +import java.util.HashMap; +import java.util.List; + +/** + * @author Isabel Román + * + */ + +public interface MetricConfigurationI { + /** + *

+ * Comprueba si la métrica está definida en el fichero por defecto o en el de la + * aplicación cliente + *

+ *

+ * También verifica que el tipo es el adecuado + *

+ * + * @param metricName nombre de la métrica que se quiere comprobar + * @param metricType tipo de la métrica + * @return metricDefinition Si la métrica está definida y el tipo es correcto se + * devuelve un mapa con las unidades y la descripción + * @throws FileNotFoundException Si hay algún problema con el fichero de + * configuración + */ + public HashMap definedMetric(String metricName, String metricType) throws FileNotFoundException; + + /** + * @param metricName nombre de la métrica que se consulta + * @return la información de la métrica en un mapa + * @throws FileNotFoundException Si hay algún problema con el fichero de + * configuración + */ + public HashMap getMetricInfo(String metricName) throws FileNotFoundException; + + /** + * @return listado con los nombres de todas las métricas disponibles + * @throws FileNotFoundException Si hay algún problema con el fichero de + * configuración + */ + List listAllMetrics() throws FileNotFoundException; +} diff --git a/src/main/java/us/muit/fs/a4i/config/doc-files/configPackage.GIF b/src/main/java/us/muit/fs/a4i/config/doc-files/configPackage.GIF deleted file mode 100644 index 1f14908f..00000000 Binary files a/src/main/java/us/muit/fs/a4i/config/doc-files/configPackage.GIF and /dev/null differ diff --git a/src/main/java/us/muit/fs/a4i/config/doc-files/configPackage.gif b/src/main/java/us/muit/fs/a4i/config/doc-files/configPackage.gif new file mode 100644 index 00000000..bcba3513 Binary files /dev/null and b/src/main/java/us/muit/fs/a4i/config/doc-files/configPackage.gif differ diff --git a/src/main/java/us/muit/fs/a4i/config/package-info.java b/src/main/java/us/muit/fs/a4i/config/package-info.java index 86d6dc6f..7ec62cf2 100644 --- a/src/main/java/us/muit/fs/a4i/config/package-info.java +++ b/src/main/java/us/muit/fs/a4i/config/package-info.java @@ -1,11 +1,15 @@ /** - *

Clases encargadas del contexto de aplicación - * Leen la configuración y la ponen disponible - * Manejan la configuración básica en ficheros simples leídos a Properties y la referida a métricas e indicadores disponibles en ficheros json

- * Paquete para la configuración + *

+ * Clases encargadas del contexto de aplicación Leen la configuración y la ponen + * disponible Manejan la configuración básica en ficheros simples leídos a + * Properties y la referida a métricas e indicadores disponibles en ficheros + * json + *

+ * Paquete para la configuración * - * @author Isabel Román - * @version 0.0 + * @author Isabel Román + * @version V.0.2 */ package us.muit.fs.a4i.config; - diff --git a/src/main/java/us/muit/fs/a4i/control/IndicatorStrategy.java b/src/main/java/us/muit/fs/a4i/control/IndicatorStrategy.java new file mode 100644 index 00000000..608406fb --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/control/IndicatorStrategy.java @@ -0,0 +1,37 @@ +package us.muit.fs.a4i.control; + +import java.util.List; + +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.model.entities.ReportItemI; + +/** + * /** + *

+ * Interfaz para calcular indicadores + *

+ * + * @param + * @author celllarod + * + */ +public interface IndicatorStrategy { + + /** + * Calcula un indicador a partir de las métricas proporcionadas. + * + * @param + * @param metrics + * @throws NotAvailableMetricException + * @return indicador + */ + public ReportItemI calcIndicator(List> metrics) throws NotAvailableMetricException; + + /** + * Obtiene las métricas necesarias + * + * @return listado de métricas + */ + public List requiredMetrics(); + +} diff --git a/src/main/java/us/muit/fs/a4i/control/IndicatorsCalculator.java b/src/main/java/us/muit/fs/a4i/control/IndicatorsCalculator.java index 37631cc3..18fb4f46 100644 --- a/src/main/java/us/muit/fs/a4i/control/IndicatorsCalculator.java +++ b/src/main/java/us/muit/fs/a4i/control/IndicatorsCalculator.java @@ -1,39 +1,61 @@ /** - *

Algoritmos para el cálculo de indicadores específicos al tipo de informe

+ *

Algoritmos para el c�lculo de indicadores espec�ficos al tipo de informe

*/ package us.muit.fs.a4i.control; import us.muit.fs.a4i.exceptions.IndicatorException; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; import us.muit.fs.a4i.model.entities.ReportI; +import us.muit.fs.a4i.control.IndicatorStrategy; /** * - *

Define los métodos para calcular cada indicador y añadirlo al informe

- *

Puede hacerse uno a uno o todos a la vez

- *

Las clases que la implementen serán específias para un tipo de informe

- * @author Isabel Román + *

+ * Define los métodos para calcular cada indicador y añadirlo al informe + *

+ *

+ * Puede hacerse uno a uno o todos a la vez + *

+ *

+ * Las clases que la implementen serán específias para un tipo de informe + *

+ * + * @author Isabel Román * */ public interface IndicatorsCalculator { /** - *

Calcula el indicador con el nombre que se pasa y lo incluye en el informe - * Si las métricas que necesita no están en el informe las busca y las añade

- * @param name Nombre del indicador a cálcular - * @param report Informe sobre el que realizar el cálculo - * @throws IndicatorException Si el indicador no está definido en la calculadora + *

+ * Calcula el indicador con el nombre que se pasa y lo incluye en el informe Si + * las métricas que necesita no están en el informe usa ReportManagerI para localizarla antes + *

+ * + * @param indicatorName Nombre del indicador a calcular + * @param reportManager Gestor del Informe sobre el que realizar el cálculo + * @throws IndicatorException Si el indicador no está definido en la calculadora */ - - public void calcIndicator(String name,ReportI report) throws IndicatorException; + + public void calcIndicator(String indicatorName, ReportManagerI reportManager) throws IndicatorException; + /** - *

Calcula todos los indicadores configurados para el tipo de informe que se pasa. Debe verificar primero que el tipo de informe que se pasa es correcto

- * @param report Informe sobre el que realizar el cálculo - * @throws IndicatorException Si el tipo del informe no coincide con el de la calculadora + *

+ * Calcula todos los indicadores configurados para el tipo de informe que se + * pasa. Debe verificar primero que el tipo de informe que se pasa es correcto + *

+ * + * @param reportManager Informe sobre el que realizar el c�lculo + * @throws IndicatorException Si el tipo del informe no coincide con el de la + * calculadora + * @throws NotAvailableMetricException Si falta alguna métrica en el informe */ - public void calcAllIndicators(ReportI report) throws IndicatorException; - + public void calcAllIndicators(ReportManagerI reportManager) throws IndicatorException; + /** * Devuelve el tipo de informe que maneja esta calculadora de indicadores + * * @return El tipo de informes */ - public ReportI.Type getReportType(); + public ReportI.ReportType getReportType(); + + public void setIndicator(String indicatorName, IndicatorStrategy strategy); } diff --git a/src/main/java/us/muit/fs/a4i/control/ReportManager.java b/src/main/java/us/muit/fs/a4i/control/ReportManager.java deleted file mode 100644 index f5aa6895..00000000 --- a/src/main/java/us/muit/fs/a4i/control/ReportManager.java +++ /dev/null @@ -1,99 +0,0 @@ -/** - * - */ -package us.muit.fs.a4i.control; - -import java.util.logging.Logger; - -import us.muit.fs.a4i.exceptions.ReportNotDefinedException; -import us.muit.fs.a4i.model.entities.ReportI; -import us.muit.fs.a4i.model.remote.RemoteEnquirer; -import us.muit.fs.a4i.persistence.PersistenceManager; -import us.muit.fs.a4i.persistence.ReportFormaterI; - -/** - * @author isa - * - */ -public class ReportManager implements ReportManagerI { - private static Logger log=Logger.getLogger(ReportManager.class.getName()); - private ReportI report; - private PersistenceManager persister; - private RemoteEnquirer enquirer; - private ReportFormaterI formater; - private IndicatorsCalculator calc; - private String entityId; - - - - @Override - public void setRemoteEnquirer(RemoteEnquirer remote) { - this.enquirer=remote; - - } - - @Override - public void setPersistenceManager(PersistenceManager persistence) { - this.persister=persistence; - - } - - @Override - public void setFormater(ReportFormaterI formater) { - this.formater=formater; - - } - - @Override - public void setIndicatorCalc(IndicatorsCalculator calc) { - this.calc=calc; - - } - - @Override - public void saveReport(ReportI report) { - persister.setReport(report); - persister.setFormater(formater); - try { - persister.saveReport(); - } catch (ReportNotDefinedException e) { - log.info("No debería entrar aquí porque se acaba de establecer el informe"); - e.printStackTrace(); - } - } - - @Override - public void save() throws ReportNotDefinedException { - if(report!=null) { - saveReport(report); - }else throw new ReportNotDefinedException(); - - } - - @Override - public ReportI createReport(String entityId) { - report=enquirer.buildReport(entityId); - return report; - } - - @Override - public void deleteReport(ReportI report) { - // TODO Auto-generated method stub - - } - - @Override - public void deleteReport() { - // TODO Auto-generated method stub - - } - - /** - * Devuelve el informe que está manejando este gestor - */ - @Override - public ReportI getReport() { - return report; - } - -} diff --git a/src/main/java/us/muit/fs/a4i/control/ReportManagerI.java b/src/main/java/us/muit/fs/a4i/control/ReportManagerI.java index f2c1c789..d7ba4c1e 100644 --- a/src/main/java/us/muit/fs/a4i/control/ReportManagerI.java +++ b/src/main/java/us/muit/fs/a4i/control/ReportManagerI.java @@ -3,72 +3,82 @@ */ package us.muit.fs.a4i.control; -import us.muit.fs.a4i.persistence.PersistenceManager; import us.muit.fs.a4i.exceptions.ReportNotDefinedException; import us.muit.fs.a4i.model.entities.ReportI; -import us.muit.fs.a4i.model.remote.RemoteEnquirer; +import us.muit.fs.a4i.model.entities.ReportItemI; import us.muit.fs.a4i.persistence.ReportFormaterI; /** - *

Interfaz con los métodos disponibles para manejar informes

- *

No depende del sistema de persistencia utilizado

- *

No depende del tipo de remoto del que se obtienen las métricas

- *

No depende del modo de calcular los indicadores

- *

En las primeras versiones sólo se leen desde remotos y se guardarán los informes localmente

- *

Versiones posteriores permitirán leer y modificar informes

- * @author Isabel Román + *

+ * Interfaz con los métodos disponibles para manejar informes + *

+ *

+ * No depende del sistema de persistencia utilizado + *

+ *

+ * No depende del tipo de remoto del que se obtienen las métricas + *

+ *

+ * No depende del modo de calcular los indicadores + *

+ *

+ * En las primeras versiones sólo se leen desde remotos y se guardarán los + * informes localmente + *

+ *

+ * Versiones posteriores permitirán leer y modificar informes + *

+ * + * @author Isabel Román * */ + public interface ReportManagerI { - - /** - *

Recupera el informe que se están manejando

- * @return Devuelve el informe manejado - */ - public ReportI getReport(); - /** - *

Establece el objeto que se usará para consultar al servidor remoto y obtener las métricas

- * @param remote Objeto RemoteEnquirer que consultará al servidor remoto - */ - - public void setRemoteEnquirer(RemoteEnquirer remote); + /** - *

Establece el objeto PersistenceManager que se encargará de guardar el informe localmente

- * @param persistence Objeto PersistenceManager concreto + *

+ * Recupera el informe que se está manejando + *

+ * + * @return Devuelve el informe manejado */ - public void setPersistenceManager(PersistenceManager persistence); + public ReportI getReport(); + + public void addMetric(String metricName); + + public ReportItemI getMetric(String metricName); + + public void addIndicator(String indicatorName); + + public void getIndicator(String indicatorName); + + public void saveReport() throws ReportNotDefinedException; + + public void deleteReport(); + /** - *

Establece el formateador a usar

+ *

+ * Establece el formateador a usar + *

+ * * @param formater El gestor de formato a utilizar */ public void setFormater(ReportFormaterI formater); - public void setIndicatorCalc(IndicatorsCalculator calc); - - /** - *

Persiste el informe que recibe como parámetro, según las reglas del gestor de persistencia y formateador establecidos

- * @param report

El informe a persistir

- */ - public void saveReport(ReportI report); - /** - *

Establecer el informe que se quiere crear

- * @throws ReportNotDefinedException Si no se había establecido un informe - */ - public void save() throws ReportNotDefinedException; - + /** - *

Crea un informe para la entidad indicada como parámetro, según las reglas del RemoteBuilder Establecido

- *

El id debe identificar unívocamente a la entidad en el remoto

- * @param id Identificador de la entidad a la que se refiere el informe + *

+ * Crea un informe para la entidad indicada como parámetro, según las reglas del + * RemoteBuilder Establecido + *

+ *

+ * El id debe identificar unívocamente a la entidad en el remoto + *

+ * + * @param entityId Identificador de la entidad a la que se refiere el informe + * @param reportType El tipo de informe * @return el informe creado + * @throws Exception el tipo de informe no coincide con el del manager */ - public ReportI createReport(String id); - /** - *

Borra el informe pasado como parámetro, según las reglas establecidas por el gestor de persistencia

- * @param report El informe que se quiere borrar - */ - public void deleteReport(ReportI report); - /** - *

Borra el informe que se está manejando actualmente, si la referencia no era nula, según las reglas establecidas por el gestor de persistencia

- */ - public void deleteReport(); + public ReportI newReport(String entityId, ReportI.ReportType reportType) throws Exception; + } diff --git a/src/main/java/us/muit/fs/a4i/control/RepositoryCalculator.java b/src/main/java/us/muit/fs/a4i/control/RepositoryCalculator.java deleted file mode 100644 index b5efe228..00000000 --- a/src/main/java/us/muit/fs/a4i/control/RepositoryCalculator.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * - */ -package us.muit.fs.a4i.control; - -import java.util.logging.Logger; - -import us.muit.fs.a4i.model.entities.Indicator; - -import us.muit.fs.a4i.model.entities.ReportI; - -/** - *

Implementa los métodos para calcular indicadores referidos a un repositorio repositorio

- *

Puede hacerse uno a uno o todos a la vez

- * @author Isabel Román - * - */ -public class RepositoryCalculator implements IndicatorsCalculator { - private static Logger log=Logger.getLogger(RepositoryCalculator.class.getName()); - @Override - public void calcIndicator(String name, ReportI report) { - log.info("Calcula el indicador de nombre "+name); - /** - * Tiene que mirar si están ya las métricas que necesita - * Si están lo calcula - * Si no están busca las métricas, las añade y lo calcula - * - */ - - } -/** - * Calcula todos los indicadores definidos para un repositorio - * Recupera todas las métricas que necesite y que no estén en el informe y las añade al mismo - * - */ - @Override - public void calcAllIndicators(ReportI report) { - log.info("Calcula todos los indicadores del repositorio y los incluye en el informe"); - } - private Indicator commitsPerUser(ReportI report) { - Indicator indicator=null; - - return indicator; - } - @Override - public ReportI.Type getReportType() { - return ReportI.Type.REPOSITORY; - } -} diff --git a/src/main/java/us/muit/fs/a4i/control/calculators/RepositoryCalculator.java b/src/main/java/us/muit/fs/a4i/control/calculators/RepositoryCalculator.java new file mode 100644 index 00000000..5081609f --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/control/calculators/RepositoryCalculator.java @@ -0,0 +1,96 @@ +/** + * + */ +package us.muit.fs.a4i.control.calculators; + +import java.util.HashMap; +import java.util.List; +import java.util.logging.Logger; + +import us.muit.fs.a4i.control.IndicatorStrategy; +import us.muit.fs.a4i.control.IndicatorsCalculator; +import us.muit.fs.a4i.control.ReportManagerI; +import us.muit.fs.a4i.exceptions.IndicatorException; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.model.entities.Indicator; +import us.muit.fs.a4i.model.entities.ReportI; +import us.muit.fs.a4i.model.entities.ReportItemI; +import java.util.stream.Collectors; + +/** + *

+ * Implementa los métodos para calcular indicadores referidos a un repositorio + *

+ *

+ * Puede hacerse uno a uno o todos a la vez + *

+ * RECUERDA: los indicadores tienen que estar incluidos en el fichero de + * configuración a4iDefault.json + * + * @author Isabel Román + * + */ +public class RepositoryCalculator implements IndicatorsCalculator { + private static Logger log = Logger.getLogger(RepositoryCalculator.class.getName()); + private static ReportI.ReportType reportType = ReportI.ReportType.REPOSITORY; + private static HashMap strategies = new HashMap<>(); + + @Override + public void calcIndicator(String indicatorName, ReportManagerI reportManager) throws IndicatorException { + log.info("Calcula el indicador de nombre " + indicatorName); + /** + * Tiene que mirar si están ya las métricas que necesita Si están lo calcula Si + * no están busca las métricas, las añade al informe y lo calcula + * + */ + IndicatorStrategy indicatorStrategy = strategies.get(indicatorName); + List requiredMetrics = indicatorStrategy.requiredMetrics(); + log.fine("Las métricas necesarias son: " + requiredMetrics.toString()); + List metrics = reportManager.getReport().getAllMetrics().stream().collect(Collectors.toList()); + List metricsName = metrics.stream().map(ReportItemI::getName).collect(Collectors.toList()); + for (String metric : requiredMetrics) { + if (!metricsName.contains(metric)) { + log.fine("se añade la métrica " + metric + " que no estaba disponible aún en el informe"); + reportManager.addMetric(metric); + } + } + + try { + // añadir el indicador al informe + reportManager.getReport().addIndicator(indicatorStrategy.calcIndicator(reportManager.getReport().getAllMetrics().stream().collect(Collectors.toList()))); + log.info("Añadido al informe indicador"); + } catch (NotAvailableMetricException e) { + log.info("No se han proporcionado todas las métricas necesarias"); + e.printStackTrace(); + } + + } + + /** + * Calcula todos los indicadores definidos para un repositorio Recupera todas + * las métricas que necesite y que no estén en el informe y las añade al mismo + * + */ + @Override + public void calcAllIndicators(ReportManagerI reportManager) throws IndicatorException { + log.info("Calcula todos los indicadores del repositorio y los incluye en el informe"); + } + + private Indicator commitsPerUser(ReportI report) { + Indicator indicator = null; + + return indicator; + } + + @Override + public ReportI.ReportType getReportType() { + return reportType; + } + + @Override + public void setIndicator(String indicatorName, IndicatorStrategy strategy) { + strategies.put(indicatorName, strategy); + + } + +} \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/control/managers/ReportManager.java b/src/main/java/us/muit/fs/a4i/control/managers/ReportManager.java new file mode 100644 index 00000000..f580b415 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/control/managers/ReportManager.java @@ -0,0 +1,235 @@ +/** + * + */ +package us.muit.fs.a4i.control.managers; + +import java.io.IOException; +import java.util.logging.Logger; + +import us.muit.fs.a4i.config.Context; +import us.muit.fs.a4i.control.IndicatorsCalculator; +import us.muit.fs.a4i.control.ReportManagerI; +import us.muit.fs.a4i.control.calculators.RepositoryCalculator; +import us.muit.fs.a4i.exceptions.ReportNotDefinedException; +import us.muit.fs.a4i.model.entities.ReportI; +import us.muit.fs.a4i.model.entities.ReportItemI; +import us.muit.fs.a4i.model.remote.GitHubRepositoryEnquirer; +import us.muit.fs.a4i.model.remote.RemoteEnquirer; +import us.muit.fs.a4i.persistence.ExcelReportManager; +import us.muit.fs.a4i.persistence.PersistenceManager; +import us.muit.fs.a4i.persistence.ReportFormater; +import us.muit.fs.a4i.persistence.ReportFormaterI; + +/** + * @author Isabel Román + * + */ +public class ReportManager implements ReportManagerI { + + private static Logger log = Logger.getLogger(ReportManager.class.getName()); + private ReportI report; + private PersistenceManager persister; + private RemoteEnquirer enquirer; + private ReportFormaterI formater; + private IndicatorsCalculator calc; + private String entityId; + private ReportI.ReportType reportType; + + public ReportManager(ReportI.ReportType reportType) throws IOException { + + this.reportType = reportType; + this.formater = new ReportFormater(); + // Selecting the RemoteEnquierer + RemoteEnquirer.RemoteType remote = null; + + remote = RemoteEnquirer.RemoteType.valueOf(Context.getContext().getRemoteType()); + log.info("Tipo de remoto configurado: " + remote); + switch (remote) { + case GITHUB: + setGHEnquierer(); + break; + default: + log.info("Tipo de persistencia no implementando"); + + } + // Selecting the persister + PersistenceManager.PersistenceType persistence = null; + persistence = PersistenceManager.PersistenceType.valueOf(Context.getContext().getPersistenceType()); + switch (persistence) { + case DDBB: + log.info("La persistencia para base de datos a�n no est� implemetnada"); + break; + case EXCEL: + this.persister = new ExcelReportManager(); + log.info("Se instancia el objeto para persistir informes en excel"); + break; + default: + break; + + } + // Selecting the IndicatorCalculator + switch (this.reportType) { + case DEVELOPER: + log.info("La calculadora para indicadores de desarrolladores no está implementada"); + break; + case ORGANIZATION: + log.info("La calculadora para indicadores de organización no está implementada"); + break; + case PROJECT: + log.info("La calculadora para indicadores de proyecto no está implementada"); + break; + case REPOSITORY: + this.calc = new RepositoryCalculator(); + log.info("Se instancia la calculadora para indicadores de respositorio"); + break; + default: + break; + + } + + } + + /** + *

+ * Borra el informe pasado como parámetro, según las reglas establecidas por el + * gestor de persistencia + *

+ * + * @param report El informe que se quiere borrar + */ + public static void deleteReport(ReportI report) { + log.info("deleteReport No implementado"); + + } + + /** + *

+ * Establece el objeto que se usará para consultar al servidor remoto y obtener + * las métricas + *

+ * + * @param remote Objeto RemoteEnquirer que consultará al servidor remoto + */ + public void setRemoteEnquirer(RemoteEnquirer remote) { + this.enquirer = remote; + + } + + public void setPersistenceManager(PersistenceManager persistence) { + this.persister = persistence; + + } + + @Override + public void setFormater(ReportFormaterI formater) { + this.formater = formater; + + } + + public void setIndicatorCalc(IndicatorsCalculator calc) { + this.calc = calc; + + } + + /** + *

+ * Persiste el informe que recibe como parámetro, según las reglas del gestor de + * persistencia y formateador establecidos + *

+ * + * @param report + *

+ * El informe a persistir + *

+ */ + public void saveReport(ReportI report) { + + persister.setFormater(formater); + try { + persister.saveReport(report); + } catch (ReportNotDefinedException e) { + log.info("El informe que se quiere guardar no está definido"); + e.printStackTrace(); + } + } + + @Override + public void saveReport() throws ReportNotDefinedException { + if (report != null) { + saveReport(report); + } else + throw new ReportNotDefinedException(); + + } + + @Override + public ReportI newReport(String entityId, ReportI.ReportType reportType) throws Exception { + if (reportType != this.reportType) { + log.info("Se está intentando crear un tipo de informe diferente al ReportManager usado"); + throw new Exception("El tipo de informe no coincide con el del ReportManager"); + } + + report = enquirer.buildReport(entityId); + return report; + } + + @Override + public void deleteReport() { + // TODO Auto-generated method stub + + } + + /** + * Devuelve el informe que está manejando este gestor + */ + @Override + public ReportI getReport() { + return report; + } + + @Override + public void addMetric(String metricName) { + // TODO Auto-generated method stub + + } + + @Override + public ReportItemI getMetric(String metricName) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void addIndicator(String indicatorName) { + // TODO Auto-generated method stub + + } + + @Override + public void getIndicator(String indicatorName) { + // TODO Auto-generated method stub + + } + + private void setGHEnquierer() { + switch (this.reportType) { + case REPOSITORY: + this.enquirer = new GitHubRepositoryEnquirer(); + log.info("Se instancia el enquierer para informes de repositorio con info de github"); + break; + case DEVELOPER: + log.info("El enquierer para los informes de desarrolladores con info de github aún no está implementado"); + break; + case ORGANIZATION: + log.info("El enquierer para los informes de la organización con info de github aún no está implementado"); + break; + case PROJECT: + log.info("El enquierer para los informes de proyecto con info de github aún no está implementado"); + break; + default: + log.info("El tipo de informe que se está solicitando no está implementado"); + break; + } + } + +} diff --git a/src/main/java/us/muit/fs/a4i/control/package-info.java b/src/main/java/us/muit/fs/a4i/control/package-info.java index 8df25b6e..f01fe61d 100644 --- a/src/main/java/us/muit/fs/a4i/control/package-info.java +++ b/src/main/java/us/muit/fs/a4i/control/package-info.java @@ -1,8 +1,13 @@ /** + *

* Clases e interfaces controladoras - * - * @author Isabel Román - * @version 0.0 + * Facilitan el cálculo de indicadores y la gestión de informes + *

+ * Paquete para clases controladoras + * + * @author Isabel Román + * @version V.0.2 */ package us.muit.fs.a4i.control; - diff --git a/src/main/java/us/muit/fs/a4i/control/strategies/ConventionsCompliantStrategy.java b/src/main/java/us/muit/fs/a4i/control/strategies/ConventionsCompliantStrategy.java new file mode 100644 index 00000000..d12a7b6f --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/control/strategies/ConventionsCompliantStrategy.java @@ -0,0 +1,152 @@ +/** + * + */ +package us.muit.fs.a4i.control.strategies; + +/** + * + */ +import java.util.Arrays; +import java.util.List; +import java.util.logging.Logger; + +import java.util.Optional; + +import us.muit.fs.a4i.control.IndicatorStrategy; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.Indicator; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItemI; + +public class ConventionsCompliantStrategy implements IndicatorStrategy { + // MARK: - Attributes + private static Logger log = Logger.getLogger(Indicator.class.getName()); + // The metrics needed to calculate the indicator output + private static final List REQUIRED_METRICS = Arrays.asList("conventionalCommits", "commitsWithDescription", + "issuesWithLabels", "gitFlowBranches", "conventionalPullRequests"); + public static final String ID = "conventionsCompliant"; + + // Weights for each metric + private double WEIGHT_CONVENTIONAL_COMMITS = 0.2; + private double WEIGHT_COMMITS_WITH_DESCRIPTION = 0.2; + private double WEIGHT_ISSUES_WITH_LABELS = 0.2; + private double WEIGHT_GIT_FLOW_BRANCHES = 0.2; + private double WEIGHT_CONVENTIONAL_PULL_REQUESTS = 0.2; + + // MARK: - Constructor + /** + * Constructor. Default weights are equal for all metrics (0.2). + */ + public ConventionsCompliantStrategy() { + super(); + } + + /** + * Constructor. Set the weights for each metric. The sum of the weights must be + * equal to 1. + * + * @param weightConventionalCommits the weight for the conventional commits + * metric + * @param weightCommitsWithDescription the weight for the commits with + * description metric + * @param weightIssuesWithLabels the weight for the issues with labels + * metric + * @param weightGitFlowBranches the weight for the git flow branches + * metric + * @param weightConventionalPullRequests the weight for the conventional pull + * requests metric + */ + public ConventionsCompliantStrategy(Double weightConventionalCommits, Double weightCommitsWithDescription, + Double weightIssuesWithLabels, Double weightGitFlowBranches, Double weightConventionalPullRequests) + throws IllegalArgumentException { + // Control that the sum of the weights is equal to 1 + if (weightConventionalCommits + weightCommitsWithDescription + weightIssuesWithLabels + weightGitFlowBranches + + weightConventionalPullRequests != 1) { + throw new IllegalArgumentException("The sum of the weights must be equal to 1"); + } + + // Set the weights + this.WEIGHT_CONVENTIONAL_COMMITS = weightConventionalCommits; + this.WEIGHT_COMMITS_WITH_DESCRIPTION = weightCommitsWithDescription; + this.WEIGHT_ISSUES_WITH_LABELS = weightIssuesWithLabels; + this.WEIGHT_GIT_FLOW_BRANCHES = weightGitFlowBranches; + this.WEIGHT_CONVENTIONAL_PULL_REQUESTS = weightConventionalPullRequests; + } + + // MARK: - Implemented methods + // Calculate the indicator output based on the provided metrics + @Override + public ReportItemI calcIndicator(List> metrics) throws NotAvailableMetricException { + // Attributes + Optional> conventionalCommits = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(0).equals(m.getName())).findAny(); + Optional> commitsWithDescription = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(1).equals(m.getName())).findAny(); + Optional> issuesWithLabels = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(2).equals(m.getName())).findAny(); + Optional> gitFlowBranches = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(3).equals(m.getName())).findAny(); + Optional> conventionalPullRequests = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(4).equals(m.getName())).findAny(); + ReportItemI indicatorReport = null; + + // Check if the required metrics are present + if (conventionalCommits.isPresent() && commitsWithDescription.isPresent() && issuesWithLabels.isPresent() + && gitFlowBranches.isPresent() && conventionalPullRequests.isPresent()) { + // Calculate the indicator + Double metric1, metric2, metric3, metric4, metric5; + // Initialize the metrics + if (conventionalCommits.get().getValue() >= 0.8499) + metric1 = conventionalCommits.get().getValue(); + else + metric1 = 0.0; + if (commitsWithDescription.get().getValue() >= 0.8499) + metric2 = commitsWithDescription.get().getValue(); + else + metric2 = 0.0; + if (issuesWithLabels.get().getValue() >= 0.8499) + metric3 = issuesWithLabels.get().getValue(); + else + metric3 = 0.0; + if (gitFlowBranches.get().getValue() >= 0.999) + metric4 = 1.0; + else + metric4 = 0.0; + if (conventionalPullRequests.get().getValue() >= 0.8499) + metric5 = conventionalPullRequests.get().getValue(); + else + metric5 = 0.0; + + Double indicatorValue = WEIGHT_CONVENTIONAL_COMMITS * metric1 + WEIGHT_COMMITS_WITH_DESCRIPTION * metric2 + + WEIGHT_ISSUES_WITH_LABELS * metric3 + WEIGHT_GIT_FLOW_BRANCHES * metric4 + + WEIGHT_CONVENTIONAL_PULL_REQUESTS * metric5; + + try { + // Create the indicator + indicatorReport = new ReportItem.ReportItemBuilder(ID, indicatorValue) + .metrics(Arrays.asList(conventionalCommits.get(), commitsWithDescription.get(), + issuesWithLabels.get(), gitFlowBranches.get(), conventionalPullRequests.get())) + .indicator(IndicatorState.UNDEFINED).build(); + } catch (ReportItemException e) { + log.info("Error en ReportItemBuilder."); + e.printStackTrace(); + } + + } else { + log.info("No se han proporcionado las métricas necesarias"); + throw new NotAvailableMetricException(REQUIRED_METRICS.toString()); + } + + // Temporarily throw + return indicatorReport; + } + + // Return the metrics required to calculate the indicator output + @Override + public List requiredMetrics() { + return REQUIRED_METRICS; + } + +} diff --git a/src/main/java/us/muit/fs/a4i/control/strategies/DeveloperPerformanceStrategy.java b/src/main/java/us/muit/fs/a4i/control/strategies/DeveloperPerformanceStrategy.java new file mode 100644 index 00000000..78f310bb --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/control/strategies/DeveloperPerformanceStrategy.java @@ -0,0 +1,89 @@ +/** + * + */ +package us.muit.fs.a4i.control.strategies; + +import us.muit.fs.a4i.control.IndicatorStrategy; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.logging.Logger; + +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.Indicator; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItemI; + +/** + * Strategy for the calculation of a developer performance comparing him with + * the rest of developers + * + * @author fracrusan (year 23/24) + * @author Isabel Román RECUERDA: los indicadores tienen que estar incluidos en + * el fichero de configuración a4iDefault.json + */ +public class DeveloperPerformanceStrategy implements IndicatorStrategy { + + private static Logger log = Logger.getLogger(Indicator.class.getName()); + // M�tricas necesarias para calcular el indicador + private static final List REQUIRED_METRICS = Arrays.asList("issuesLastMonth", "closedIssuesLastMonth", + "issues4DevLastMonth", "meanClosedIssuesLastMonth"); + + @Override + public ReportItemI calcIndicator(List metrics) throws NotAvailableMetricException { + // Se obtienen y se comprueba que se pasan las m�tricas necesarias para calcular + // el indicador. + Optional> issuesLastMonth = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(0).equals(((ReportItemI) m).getName())).findAny(); + Optional> closedIssuesLastMonth = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(1).equals(((ReportItemI) m).getName())).findAny(); + Optional> issues4DevLastMonth = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(2).equals(((ReportItemI) m).getName())).findAny(); + Optional> meanClosedIssuesLastMonth = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(3).equals(((ReportItemI) m).getName())).findAny(); + ReportItemI indicatorReport = null; + + if (issuesLastMonth.isPresent() && closedIssuesLastMonth.isPresent() && issues4DevLastMonth.isPresent() + && meanClosedIssuesLastMonth.isPresent()) { + Double rendimientoMiembro; + + // calculating indicator + if (issuesLastMonth.get().getValue() != 0 && issues4DevLastMonth.get().getValue() != 0 + && meanClosedIssuesLastMonth.get().getValue() != 0) + rendimientoMiembro = (closedIssuesLastMonth.get().getValue() / issuesLastMonth.get().getValue()) + / (meanClosedIssuesLastMonth.get().getValue() / issues4DevLastMonth.get().getValue()); + else if (meanClosedIssuesLastMonth.get().getValue() != 0) + rendimientoMiembro = 1.0; + else + rendimientoMiembro = 0.0; + + try { + // Se crea el indicador + indicatorReport = new ReportItem.ReportItemBuilder("developerPerformance", rendimientoMiembro) + .metrics(Arrays.asList(issuesLastMonth.get(), closedIssuesLastMonth.get(), + issues4DevLastMonth.get(), meanClosedIssuesLastMonth.get())) + .indicator(IndicatorState.UNDEFINED).build(); + } catch (ReportItemException e) { + log.info("Error en ReportItemBuilder."); + e.printStackTrace(); + } + + } else { + log.info("No se han proporcionado las m�tricas necesarias"); + throw new NotAvailableMetricException(REQUIRED_METRICS.toString()); + } + + return indicatorReport; + } + + @Override + public List requiredMetrics() { + // Para calcular el indicador "rendimientoMiembro", ser�n necesarias las + // m�tricas + // "openIssues" y "closedIssues". + return REQUIRED_METRICS; + } + +} diff --git a/src/main/java/us/muit/fs/a4i/control/strategies/FixTimeStrategy.java b/src/main/java/us/muit/fs/a4i/control/strategies/FixTimeStrategy.java new file mode 100644 index 00000000..7014d677 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/control/strategies/FixTimeStrategy.java @@ -0,0 +1,84 @@ +/** + * + */ +package us.muit.fs.a4i.control.strategies; + +/** + * + */ +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.logging.Logger; + +import us.muit.fs.a4i.control.IndicatorStrategy; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.Indicator; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItemI; + +/** + * Estrategia de indicador equipo 6 del curso 23/24, no coincide con lo que + * decían ni las métricas están incluidas en ningún Enquirer Aparece una clase + * Equirer MetricasG6.java pero es una copia incompleta del que se les da y no + * añade los indicadores que aquí aparecen + * REMEMBER: metrics and indicators must be included in a4iDefault.json + */ + +public class FixTimeStrategy implements IndicatorStrategy { + + private static Logger log = Logger.getLogger(Indicator.class.getName()); + + // M�tricas necesarias para calcular el indicador + private static final List REQUIRED_METRICS = Arrays.asList("clasificacion", "correccion", "num_errores"); + + @Override + public ReportItemI calcIndicator(List> metrics) throws NotAvailableMetricException { + // Se obtienen y se comprueba que se pasan las m�tricas necesarias para calcular + // el indicador. + Optional> clasificacion = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(0).equals(m.getName())).findAny(); + Optional> correccion = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(1).equals(m.getName())).findAny(); + Optional> num_errores = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(2).equals(m.getName())).findAny(); + ReportItemI indicatorReport = null; + + if (clasificacion.isPresent() && correccion.isPresent() && num_errores.isPresent()) { + Double procesoDeIssues; + + // Se realiza el c�lculo del indicador + if (clasificacion.get().getValue() != 0 && correccion.get().getValue() != 0 + && num_errores.get().getValue() != 0) + procesoDeIssues = (clasificacion.get().getValue() + correccion.get().getValue()) + / num_errores.get().getValue(); + else + procesoDeIssues = 0.0; + + try { + // Se crea el indicador + indicatorReport = new ReportItem.ReportItemBuilder("fixTime", procesoDeIssues) + .metrics(Arrays.asList(clasificacion.get(), correccion.get(), num_errores.get())) + .indicator(IndicatorState.UNDEFINED).build(); + } catch (ReportItemException e) { + log.info("Error en ReportItemBuilder."); + e.printStackTrace(); + } + + } else { + log.info("No se han proporcionado las m�tricas necesarias"); + throw new NotAvailableMetricException(REQUIRED_METRICS.toString()); + } + + return indicatorReport; + } + + @Override + public List requiredMetrics() { + // Para calcular el indicador "IssuesRatio", ser�n necesarias las m�tricas + // "openIssues" y "closedIssues". + return REQUIRED_METRICS; + } +} \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/control/strategies/IEFStrategy.java b/src/main/java/us/muit/fs/a4i/control/strategies/IEFStrategy.java new file mode 100644 index 00000000..627c8127 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/control/strategies/IEFStrategy.java @@ -0,0 +1,86 @@ +package us.muit.fs.a4i.control.strategies; + +import us.muit.fs.a4i.control.IndicatorStrategy; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItemI; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.Collection; + + +/** + * Estrategia para calcular el Ãndice de Eficiencia del Flujo (IEF). + */ +public class IEFStrategy implements IndicatorStrategy { + private static final Logger log = Logger.getLogger(IEFStrategy.class.getName()); + + private static final List REQUIRED = List.of( + "cycleTime", "waitTime", "throughput", "WIP" + ); + + private final double w1 = 0.25, w2 = 0.25, w3 = 0.25, w4 = 0.25; + private final double maxC = 160.0, maxW = 80.0, maxT = 50.0, maxP = 20.0; + + @Override + public ReportItemI calcIndicator(List> metrics) + throws NotAvailableMetricException { + log.info("Calculando IEF con métricas: " + metrics); + Map m = metrics.stream() + .collect(Collectors.toMap(ReportItemI::getName, ReportItemI::getValue)); + + for (String req : REQUIRED) { + if (!m.containsKey(req)) { + throw new NotAvailableMetricException("Falta métrica " + req); + } + } + + double cycle = m.get("cycleTime"), + wait = m.get("waitTime"), + thr = m.get("throughput"), + wip = m.get("WIP"); + + double normC = clamp(cycle / maxC), + normW = clamp(wait / maxW), + normT = clamp(thr / maxT), + normP = clamp(wip / maxP); + + double score = w1 * (1 - normC) + + w2 * (1 - normW) + + w3 * normT + + w4 * (1 - normP); + + IndicatorState state = classify(score); + + try { + return new ReportItem.ReportItemBuilder("IEF", score) + .metrics((Collection) metrics) + .indicator(state) + .build(); + } catch (ReportItemException e) { + throw new NotAvailableMetricException("Error building IEF ReportItem: " + e.getMessage()); + } + } + + @Override + public List requiredMetrics() { + return REQUIRED; + } + + private double clamp(double v) { + return v < 0 ? 0 : (v > 1 ? 1 : v); + } + + private IndicatorState classify(double x) { + if (x >= 0.8) return IndicatorState.OK; + if (x >= 0.6) return IndicatorState.OK; + if (x >= 0.4) return IndicatorState.WARNING; + return IndicatorState.CRITICAL; + } +} diff --git a/src/main/java/us/muit/fs/a4i/control/strategies/IssuesRatioIndicatorStrategy.java b/src/main/java/us/muit/fs/a4i/control/strategies/IssuesRatioIndicatorStrategy.java new file mode 100644 index 00000000..38f62f4c --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/control/strategies/IssuesRatioIndicatorStrategy.java @@ -0,0 +1,70 @@ +package us.muit.fs.a4i.control.strategies; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.logging.Logger; + +import us.muit.fs.a4i.control.IndicatorStrategy; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.Indicator; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItemI; + +/** + * @author alumnos del curso 23/24, grupo 3 RECUERDA: los indicadores tienen que + * estar incluidos en el fichero de configuración a4iDefault.json + */ +public class IssuesRatioIndicatorStrategy implements IndicatorStrategy { + + private static Logger log = Logger.getLogger(Indicator.class.getName()); + + // M�tricas necesarias para calcular el indicador + private static final List REQUIRED_METRICS = Arrays.asList("openIssues", "closedIssues"); + + @Override + public ReportItemI calcIndicator(List> metrics) throws NotAvailableMetricException { + // Se obtienen y se comprueba que se pasan las m�tricas necesarias para calcular + // el indicador. + Optional> openIssues = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(0).equals(m.getName())).findAny(); + Optional> closedIssues = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(1).equals(m.getName())).findAny(); + ReportItemI indicatorReport = null; + + if (openIssues.isPresent() && closedIssues.isPresent()) { + Double issuesRatio; + + // Se realiza el c�lculo del indicador + if (closedIssues.get().getValue() != 0) + issuesRatio = openIssues.get().getValue() / closedIssues.get().getValue(); + else + issuesRatio = openIssues.get().getValue(); + + try { + // Se crea el indicador + indicatorReport = new ReportItem.ReportItemBuilder("issuesRatio", issuesRatio) + .metrics(Arrays.asList(openIssues.get(), closedIssues.get())) + .indicator(IndicatorState.UNDEFINED).build(); + } catch (ReportItemException e) { + log.info("Error en ReportItemBuilder."); + e.printStackTrace(); + } + + } else { + log.info("No se han proporcionado las m�tricas necesarias"); + throw new NotAvailableMetricException(REQUIRED_METRICS.toString()); + } + + return indicatorReport; + } + + @Override + public List requiredMetrics() { + // Para calcular el indicador "IssuesRatio", ser�n necesarias las m�tricas + // "openIssues" y "closedIssues". + return REQUIRED_METRICS; + } +} \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/control/strategies/PRPerformanceStrategy.java b/src/main/java/us/muit/fs/a4i/control/strategies/PRPerformanceStrategy.java new file mode 100644 index 00000000..e6c8ab9f --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/control/strategies/PRPerformanceStrategy.java @@ -0,0 +1,92 @@ +/** + * + */ +package us.muit.fs.a4i.control.strategies; + +/** + * Strategy for the calculation of the indicator PRPerformance + * REMEMBER: metrics must be included in a4iDefault.json + */ + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.logging.Logger; + +import us.muit.fs.a4i.control.IndicatorStrategy; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.Indicator; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItemI; + +public class PRPerformanceStrategy implements IndicatorStrategy { + + private static Logger log = Logger.getLogger(Indicator.class.getName()); + + // M�tricas necesarias para calcular el indicador + private static final List REQUIRED_METRICS = Arrays.asList("pullRequestsAcceptedLastYear", + "pullRequestsAcceptedLastMonth", "pullRequestsRejectedLastMonth", "pullRequestsRejectedLastYear"); + + @Override + public ReportItemI calcIndicator(List> metrics) throws NotAvailableMetricException { + + // Obtener las métricas necesarias + Optional> pullRequestsAcceptedLastYear = metrics.stream() + .filter(m -> "pullRequestsAcceptedLastYear".equals(m.getName())).findAny(); + Optional> pullRequestsAcceptedLastMonth = metrics.stream() + .filter(m -> "pullRequestsAcceptedLastMonth".equals(m.getName())).findAny(); + Optional> pullRequestsRejectedLastYear = metrics.stream() + .filter(m -> "pullRequestsRejectedLastYear".equals(m.getName())).findAny(); + Optional> pullRequestsRejectedLastMonth = metrics.stream() + .filter(m -> "pullRequestsRejectedLastMonth".equals(m.getName())).findAny(); + + ReportItemI indicatorReport = null; + + // Comprobar que todas las métricas necesarias están presentes + if (pullRequestsAcceptedLastYear.isPresent() && pullRequestsAcceptedLastMonth.isPresent() + && pullRequestsRejectedLastYear.isPresent() && pullRequestsRejectedLastMonth.isPresent()) { + + // Obtener los valores de las métricas + Double acceptedLastYear = pullRequestsAcceptedLastYear.get().getValue(); + Double acceptedLastMonth = pullRequestsAcceptedLastMonth.get().getValue(); + Double rejectedLastYear = pullRequestsRejectedLastYear.get().getValue(); + Double rejectedLastMonth = pullRequestsRejectedLastMonth.get().getValue(); + + // Calcular el ratio de issues + Double pullRequestsAcceptance = 0.0; + if (acceptedLastYear != 0 && acceptedLastMonth != 0) { + pullRequestsAcceptance = rejectedLastYear / acceptedLastYear - rejectedLastMonth / acceptedLastMonth; + } else { + // Si una compañía productora de software no ha hecho un PR en el último mes, + // mal va. + pullRequestsAcceptance = 1.0; + } + + try { + // Crear el indicador + indicatorReport = new ReportItem.ReportItemBuilder("PRPerformance", pullRequestsAcceptance) + .metrics(Arrays.asList(pullRequestsAcceptedLastYear.get(), pullRequestsAcceptedLastMonth.get(), + pullRequestsRejectedLastYear.get(), pullRequestsRejectedLastMonth.get())) + .indicator(IndicatorState.OK).build(); + } catch (ReportItemException e) { + log.info("Error en ReportItemBuilder."); + e.printStackTrace(); + } + + } else { + log.info("No se han proporcionado las métricas necesarias"); + throw new NotAvailableMetricException(REQUIRED_METRICS.toString()); + } + + return indicatorReport; + } + + @Override + public List requiredMetrics() { + // Para calcular el indicador "IssuesRatio", ser�n necesarias las m�tricas + // "openIssues" y "closedIssues". + return REQUIRED_METRICS; + } +} diff --git a/src/main/java/us/muit/fs/a4i/control/strategies/PullRequestIndicatorStrategy.java b/src/main/java/us/muit/fs/a4i/control/strategies/PullRequestIndicatorStrategy.java new file mode 100644 index 00000000..f83562e9 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/control/strategies/PullRequestIndicatorStrategy.java @@ -0,0 +1,88 @@ +/** + * + */ +package us.muit.fs.a4i.control.strategies; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.logging.Logger; + +import us.muit.fs.a4i.control.IndicatorStrategy; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItemI; + +/** + * % de pull requests cerrados, sobre el total + * + * @author Sergio García López (equipo 4 del curso 23/24) RECUERDA: los + * indicadores tienen que estar incluidos en el fichero de configuración + * a4iDefault.json + * + */ +public class PullRequestIndicatorStrategy implements IndicatorStrategy { + private static Logger log = Logger.getLogger(PullRequestIndicatorStrategy.class.getName()); + + // Métricas necesarias para calcular el indicador + private static final List REQUIRED_METRICS = Arrays.asList("totalPullReq", "closedPullReq"); + + @Override + public ReportItemI calcIndicator(List> metrics) throws NotAvailableMetricException { + + // Indicador a devolver + ReportItemI indicatorReport = null; + + // Estado del indicador + IndicatorState estado = IndicatorState.UNDEFINED; + + Optional> totalPullReq = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(0).equals(m.getName())).findAny(); + Optional> closedPullReq = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(1).equals(m.getName())).findAny(); + + if (totalPullReq.isPresent() && closedPullReq.isPresent()) { + + // Calculamos el indicador + Double pullRequestCompletion = 0.0; + + if (totalPullReq.get().getValue() > 0) { + pullRequestCompletion = 100 * closedPullReq.get().getValue() / totalPullReq.get().getValue(); + + // Criterios de calidad (porcentuales) + // estos límites deben estar configurados en el fichero a4iDefault.json, no aquí + if (pullRequestCompletion > 75) { + estado = IndicatorState.OK; + } else if (pullRequestCompletion > 50) { + estado = IndicatorState.WARNING; + } else { + estado = IndicatorState.CRITICAL; + } + } + + try { + + indicatorReport = new ReportItem.ReportItemBuilder("pullRequestCompletion", + pullRequestCompletion).metrics(Arrays.asList(totalPullReq.get(), closedPullReq.get())) + .indicator(estado).build(); + + } catch (ReportItemException e) { + log.info("Error en ReportItemBuilder: " + e); + } + + } else { + log.info("Falta alguna de las métricas"); + throw new NotAvailableMetricException(REQUIRED_METRICS.toString()); + } + + return indicatorReport; + + } + + @Override + public List requiredMetrics() { + return REQUIRED_METRICS; + } +} diff --git a/src/main/java/us/muit/fs/a4i/control/strategies/RepoActivityStrategy.java b/src/main/java/us/muit/fs/a4i/control/strategies/RepoActivityStrategy.java new file mode 100644 index 00000000..82ce0fc8 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/control/strategies/RepoActivityStrategy.java @@ -0,0 +1,124 @@ +/** + * + */ +package us.muit.fs.a4i.control.strategies; + +import java.util.Arrays; +import java.util.List; +import java.util.logging.Logger; + +import us.muit.fs.a4i.control.IndicatorStrategy; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItemI; + +/** + * Clase para calcular el indicador compuesto de issues, pull requests y + * contribuyentes. + * + * @param Tipo de dato del indicador. + */ +public class RepoActivityStrategy implements IndicatorStrategy { + private static Logger log = Logger.getLogger(RepoActivityStrategy.class.getName()); + + // Métricas necesarias para calcular el indicador + private static final List REQUIRED_METRICS = Arrays.asList("closedIssues", "issues", "closedPullReq", + "totalPullReq", "activeContributor", "totalContributor"); + + @Override + public ReportItemI calcIndicator(List> metricas) throws NotAvailableMetricException { + + // Variables para almacenar las métricas + ReportItemI issuesCerrados = null; + ReportItemI issuesTotales = null; + ReportItemI pullRequestsCerrados = null; + ReportItemI pullRequestsTotales = null; + ReportItemI contribuyentesActivos = null; + ReportItemI contribuyentesTotales = null; + + // Indicador a devolver + ReportItemI indicatorReport = null; + + // Estado del indicador + IndicatorState estado = IndicatorState.UNDEFINED; + + // Buscamos las métricas en la lista + for (ReportItemI metrica : metricas) { + switch (metrica.getName()) { + case "closedIssues": + issuesCerrados = metrica; + break; + case "issues": + issuesTotales = metrica; + break; + case "closedPullReq": + pullRequestsCerrados = metrica; + break; + case "totalPullReq": + pullRequestsTotales = metrica; + break; + case "activeContributor": + contribuyentesActivos = metrica; + break; + case "totalContributor": + contribuyentesTotales = metrica; + break; + } + } + + if (issuesCerrados != null && issuesTotales != null && pullRequestsCerrados != null + && pullRequestsTotales != null && contribuyentesActivos != null && contribuyentesTotales != null) { + + // Verificamos que los valores totales no sean cero para evitar divisiones por + // cero + if (issuesTotales.getValue() == 0 || pullRequestsTotales.getValue() == 0 + || contribuyentesTotales.getValue() == 0) { + throw new NotAvailableMetricException("Los valores totales no pueden ser cero."); + } + + // Calculamos el indicador + double issuesRatio = (issuesCerrados.getValue() / issuesTotales.getValue()) * 40; + double pullRequestsRatio = (pullRequestsCerrados.getValue() / pullRequestsTotales.getValue()) * 40; + double contribuyentesRatio = (contribuyentesActivos.getValue() / contribuyentesTotales.getValue()) * 20; + + double indicador = issuesRatio + pullRequestsRatio + contribuyentesRatio; + + // Criterios de calidad + if (indicador >= 90) { + estado = IndicatorState.OK; + } else if (indicador >= 80) { + estado = IndicatorState.WARNING; + } else if (indicador >= 70) { + estado = IndicatorState.WARNING; + } else if (indicador >= 50) { + estado = IndicatorState.CRITICAL; + } else if (indicador >= 26) { + estado = IndicatorState.CRITICAL; + } else { + estado = IndicatorState.UNDEFINED; + } + + try { + indicatorReport = new ReportItem.ReportItemBuilder("repoActivity", indicador) + .metrics(Arrays.asList(issuesCerrados, issuesTotales, pullRequestsCerrados, pullRequestsTotales, + contribuyentesActivos, contribuyentesTotales)) + .indicator(estado).build(); + } catch (ReportItemException e) { + log.info("Error en ReportItemBuilder: " + e); + } + + } else { + log.info("Falta alguna de las métricas"); + throw new NotAvailableMetricException(REQUIRED_METRICS.toString()); + } + + return indicatorReport; + } + + @Override + public List requiredMetrics() { + return REQUIRED_METRICS; + } +} diff --git a/src/main/java/us/muit/fs/a4i/control/strategies/TeamsBalanceStrategy.java b/src/main/java/us/muit/fs/a4i/control/strategies/TeamsBalanceStrategy.java new file mode 100644 index 00000000..5c568713 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/control/strategies/TeamsBalanceStrategy.java @@ -0,0 +1,79 @@ +/** + * + */ +package us.muit.fs.a4i.control.strategies; + +import us.muit.fs.a4i.control.IndicatorStrategy; +/** + * + */ +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.Indicator; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; + +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItemI; + +import java.util.logging.Logger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.lang.Math; +import java.lang.Double; + +public class TeamsBalanceStrategy implements IndicatorStrategy { + + private static Logger log = Logger.getLogger(Indicator.class.getName()); + + // M�tricas necesarias para calcular el indicador + private static final List REQUIRED_METRICS = Arrays.asList("teamsBalance", "repositories"); + + @Override + public ReportItemI calcIndicator(List> metrics) throws NotAvailableMetricException { + // Se obtienen y se comprueba que se pasan las m�tricas necesarias para calcular + // el indicador. + Optional> teamsBalance = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(0).equals(m.getName())).findAny(); + Optional> repositories = metrics.stream() + .filter(m -> REQUIRED_METRICS.get(1).equals(m.getName())).findAny(); + ReportItemI indicatorReport = null; + log.info("Teamsbalance: " + teamsBalance.toString()); + log.info("Repositories: " + repositories.toString()); + if (teamsBalance.isPresent() && repositories.isPresent()) { + Double teamsBalanceResult; + + // Se realiza el c�lculo del indicador + teamsBalanceResult = (Double) teamsBalance.get().getValue() * 100 / repositories.get().getValue(); + + log.info("teamsBalanceResult: " + teamsBalanceResult.toString()); + try { + // Se crea el indicador + // No se entiende por qué el indicador tiene el mismo nombere que una de las + // métricas + indicatorReport = new ReportItem.ReportItemBuilder("teamsBalance", teamsBalanceResult) + .metrics(Arrays.asList(teamsBalance.get(), repositories.get())) + .indicator(IndicatorState.UNDEFINED).build(); + + log.info("IndicatorReport: " + indicatorReport.toString()); + } catch (ReportItemException e) { + log.info("Error en ReportItemBuilder."); + e.printStackTrace(); + } + + } else { + log.info("No se han proporcionado las métricas necesarias"); + throw new NotAvailableMetricException(REQUIRED_METRICS.toString()); + } + + return indicatorReport; + } + + @Override + public List requiredMetrics() { + // Para calcular el indicador "IssuesRatio", ser�n necesarias las m�tricas + // "openIssues" y "closedIssues". + return REQUIRED_METRICS; + } +} diff --git a/src/main/java/us/muit/fs/a4i/exceptions/IndicatorException.java b/src/main/java/us/muit/fs/a4i/exceptions/IndicatorException.java index f8da006b..1e714cbe 100644 --- a/src/main/java/us/muit/fs/a4i/exceptions/IndicatorException.java +++ b/src/main/java/us/muit/fs/a4i/exceptions/IndicatorException.java @@ -1,28 +1,32 @@ package us.muit.fs.a4i.exceptions; /** - * @author Isabel Román + * @author Isabel Román * */ public class IndicatorException extends Exception { - /** - * Excepción que indica que se está intentando recuperar una entidad sin haber establecido su id + /** + * Excepción al manejar Indicador */ private static final long serialVersionUID = 1L; /** - * Información sobre el error + * Información sobre el error */ private String message; + /** - *

Constructor

+ *

+ * Constructor + *

+ * * @param info Mensaje definiendo el error */ - public IndicatorException(String info){ - message=info; + public IndicatorException(String info) { + message = info; } @Override - public String getMessage(){ - return message; - } + public String getMessage() { + return message; + } } \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/exceptions/MetricException.java b/src/main/java/us/muit/fs/a4i/exceptions/MetricException.java index cfbe3837..84cef6fe 100644 --- a/src/main/java/us/muit/fs/a4i/exceptions/MetricException.java +++ b/src/main/java/us/muit/fs/a4i/exceptions/MetricException.java @@ -1,28 +1,32 @@ package us.muit.fs.a4i.exceptions; /** - * @author Isabel Román + * @author Isabel Román * */ -public class MetricException extends Exception { - /** - * Excepción que indica que se está intentando recuperar una entidad sin haber establecido su id +public class MetricException extends RuntimeException { + /** + * Excepción al manejar métrica */ private static final long serialVersionUID = 1L; /** - * Información sobre el error + * Información sobre el error */ private String message; + /** - *

Constructor

+ *

+ * Constructor + *

+ * * @param info Mensaje definiendo el error */ - public MetricException(String info){ - message=info; + public MetricException(String info) { + message = info; } @Override - public String getMessage(){ - return message; - } + public String getMessage() { + return message; + } } \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/exceptions/NotAvailableMetricException.java b/src/main/java/us/muit/fs/a4i/exceptions/NotAvailableMetricException.java new file mode 100644 index 00000000..d704b701 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/exceptions/NotAvailableMetricException.java @@ -0,0 +1,29 @@ +package us.muit.fs.a4i.exceptions; + +public class NotAvailableMetricException extends Exception{ + + /** + * Excepcion que ocurre cuando se quiere calcular un indicador pero no se proporcionan las m�tricas adecuadas. + */ + private static final long serialVersionUID = 1L; + /** + * Informacion sobre el error + */ + private String message; + + /** + *

+ * Constructor + *

+ * + * @param info Mensaje definiendo el error + */ + public NotAvailableMetricException(String info) { + message = "No se dispone de las métricas necesiarias " + info; + } + + @Override + public String getMessage() { + return message; + } +} \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/exceptions/ReportItemException.java b/src/main/java/us/muit/fs/a4i/exceptions/ReportItemException.java new file mode 100644 index 00000000..0ad53e37 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/exceptions/ReportItemException.java @@ -0,0 +1,32 @@ +package us.muit.fs.a4i.exceptions; + +/** + * @author Isabel Román + * + */ +public class ReportItemException extends Exception { + /** + * Excepción al manejar ReportItem + */ + private static final long serialVersionUID = 1L; + /** + * Información sobre el error + */ + private String message; + + /** + *

+ * Constructor + *

+ * + * @param info Mensaje definiendo el error + */ + public ReportItemException(String info) { + message = info; + } + + @Override + public String getMessage() { + return message; + } +} \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/exceptions/ReportNotDefinedException.java b/src/main/java/us/muit/fs/a4i/exceptions/ReportNotDefinedException.java index f5bd135e..fae3bc0a 100644 --- a/src/main/java/us/muit/fs/a4i/exceptions/ReportNotDefinedException.java +++ b/src/main/java/us/muit/fs/a4i/exceptions/ReportNotDefinedException.java @@ -8,13 +8,14 @@ * */ public class ReportNotDefinedException extends Exception { - /** - * Excepción que indica que se está intentando recuperar una entidad sin haber establecido su id + /** + * Excepción que indica que se está intentando recuperar una entidad sin haber + * establecido su id */ private static final long serialVersionUID = 1L; @Override - public String getMessage(){ - return "No se ha establecido aún un informe"; - } + public String getMessage() { + return "No se ha establecido aún un informe"; + } } diff --git a/src/main/java/us/muit/fs/a4i/exceptions/package-info.java b/src/main/java/us/muit/fs/a4i/exceptions/package-info.java index 95b08f69..868de094 100644 --- a/src/main/java/us/muit/fs/a4i/exceptions/package-info.java +++ b/src/main/java/us/muit/fs/a4i/exceptions/package-info.java @@ -1,8 +1,9 @@ /** - *

Excepciones propias de la aplicación

- * Paquete para excepciones - * @author Isabel Román - * @version 0.0 + *

+ * Excepciones propias de la aplicación + *

+ * + * @author Isabel Román + * @version V.0.2 */ package us.muit.fs.a4i.exceptions; - diff --git a/src/main/java/us/muit/fs/a4i/model/entities/Font.java b/src/main/java/us/muit/fs/a4i/model/entities/Font.java new file mode 100644 index 00000000..f0024ecd --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/model/entities/Font.java @@ -0,0 +1,75 @@ +package us.muit.fs.a4i.model.entities; +import java.awt.Color; +import java.util.logging.Logger; + + +public class Font { + private static Logger log = Logger.getLogger(Font.class.getName()); + private Color color; + private java.awt.Font font; + /** + * Todos los valores por defecto + */ + public Font() { + this.color = Color.black; + this.font = new java.awt.Font ("Serif", java.awt.Font.PLAIN , 10); + } + /** + * Personalizando el color + * @param color color + */ + public Font (String color) { + this.font = new java.awt.Font ("Serif", java.awt.Font.PLAIN , 10); + setColor(color); + } + /** + * Todos los Valores personalizados salvo el estilo + * @param family tipo de letra + * @param size tamaño + * @param color color + */ + public Font (String family, int size, String color) { + log.info("Creando fuente con valores familia "+family+" tamano "+size+" color "+color); + this.font=new java.awt.Font (family.toLowerCase(), java.awt.Font.PLAIN , size); + setColor(color); + log.info("Se ha creado fuente con el tipo "+font.getFamily()+" y el color "+color); + } + /** + * Todos los Valores personalizados + * @param family tipo de letra + * @param format formato + * @param size tamaño + * @param color color + */ + public Font (String family, int style, int size, String color) { + this.font=new java.awt.Font (family.toLowerCase(), style , size); + setColor(color); + } + + public Color getColor() { + return this.color; + } + public java.awt.Font getFont() { + return this.font; + } + + private void setColor(String color) { + switch(color.toLowerCase()){ + case "red","rojo": + this.color=Color.RED; + break; + case "green","verde": + this.color=Color.GREEN; + break; + case "blue","azul": + this.color=Color.BLUE; + break; + case "orange","naranja": + this.color=Color.ORANGE; + break; + default: + this.color=Color.BLACK; + } + } + +} \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/model/entities/Indicator.java b/src/main/java/us/muit/fs/a4i/model/entities/Indicator.java index f8af021b..a1e10a9e 100644 --- a/src/main/java/us/muit/fs/a4i/model/entities/Indicator.java +++ b/src/main/java/us/muit/fs/a4i/model/entities/Indicator.java @@ -3,167 +3,60 @@ */ package us.muit.fs.a4i.model.entities; -import java.time.LocalDateTime; -import java.time.ZoneOffset; +import java.util.Collection; import java.util.Date; import java.util.logging.Logger; - /** - * @author Isabel Román + * @author Isabel Román * */ -public class Indicator { - private static Logger log=Logger.getLogger(Indicator.class.getName()); - private Date date; - private String source; - private String description; - private String name; - private T value; - private String unit; - private State state; +public class Indicator implements IndicatorI{ + private static Logger log = Logger.getLogger(Indicator.class.getName()); + private Collection metrics; + private IndicatorState state; /** - *

Estados posibles del indicador, indican el grado de atención que requiere por parte del analista

- * @author Isabel Román - * - */ - public static enum State{ - OK, - WARNING, - CRITICAL - }; - /** - *

Constructor privado, para construir desde fuera hay que usar el método build de IndicatorBuilder. Implementación patrón constructor

- * @param builder Clase constructora - */ - private Indicator(IndicatorBuilder builder){ - - this.description=builder.description; - this.name=builder.name; - this.value=builder.value; - this.source=builder.source; - this.unit=builder.unit; - this.date=builder.date; - this.state=builder.state; - } - /** - * - * @return Descripción del indicador - */ - public String getDescription() { - return description; - } - /** - * - * @return Nombre del indicador + * Constructor */ - public String getName() { - return name; + public Indicator() { + this.state = IndicatorState.UNDEFINED; } + /** - * - * @return Unidades de medida del indicador + * @param state Estado del nuevo indicador + * @param metrics Colección de métricas en las que se basa el indicador */ - public String getUnit() { - return unit; + public Indicator(IndicatorState state, Collection metrics) { + this.metrics = metrics; + this.state = state; } - /** - * - * @return Valor del indicador - */ - public T getValue() { - return value; + + public void setMetrics(Collection metrics) { + this.metrics = metrics; } - /** - *

Clase constructora, se usará el método build para crear un objeto Indicator desde fuera

- * @author Isabel Román - * - * @param Tipo utilizado en el valor del indicador que va a construir - */ - public static class IndicatorBuilder{ - private String description; - private String name; - private Date date; - private T value; - private String source; - private String unit; - private State state; - /** - *

Constructor de la clase constructora, recibe los parámetros obligatgorios nombre y valor del indicador

- * @param indicatorName Nombre del indicador - * @param indicatorValue Valor del indicador - */ - public IndicatorBuilder(String indicatorName, T indicatorValue) { - this.name=indicatorName; - this.value=indicatorValue; - this.date=Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)); - } - /** - *

Establece la descripción en el constructor

- * @param description descripción del indicador - * @return el objeto constructor - */ - public IndicatorBuilder description(String description){ - this.description=description; - return this; - } - /** - * - * @param source Fuente de la que se obtuvo el indicador - * @return el objeto constructor - */ - public IndicatorBuilder source(String source){ - this.source=source; - return this; - } - /** - * - * @param unit Unidad de medida del indicador - * @return el objeto constructor - */ - public IndicatorBuilder unit(String unit){ - this.unit=unit; - return this; - } - /** - * - * @param state Estado del indicador. Grado de atención que necesita por parte del analista - * @return el objeto constructor - */ - public IndicatorBuilder state(State state){ - this.state=state; - return this; - } - /** - *

Método de construcción del indicador, patrón constructor

- * @return El indicador construido - */ - public Indicator build(){ - return new Indicator(this); - } + + public void setState(IndicatorState state) { + this.state = state; } + @Override public String toString() { String info; - info="Indicador para "+description+", con valor=" + value + ", source=" + source - + ", unit=" + unit +" fecha de cálculo= "+ date+" Estado= "+state; + info = "Indicador estado " + state + ", a partir de las metricas: " + metrics; return info; } - /** - * - * @return Fecha de creación del indicador - */ - public Date getDate() { - return date; + + @Override + public IndicatorState getState() { + + return state; } - - /** - * - * @return Fuente de los datos para obtener el indicador - */ - public String getSource() { - return source; + + @Override + public Collection getMetrics() { + + return metrics; } - + } \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/model/entities/IndicatorI.java b/src/main/java/us/muit/fs/a4i/model/entities/IndicatorI.java new file mode 100644 index 00000000..75385477 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/model/entities/IndicatorI.java @@ -0,0 +1,54 @@ +/** + * + */ +package us.muit.fs.a4i.model.entities; + +import java.util.Collection; + +/** + * @author Isabel Román Interfaz para manejar los indicadores + * + */ +public interface IndicatorI { + /** + *

+ * Estados posibles del indicador, indican el grado de atención que requiere por + * parte del analista + *

+ * + * @author Isabel Román + * + */ + public static enum IndicatorState { + OK, WARNING, CRITICAL, UNDEFINED + } + + /** + * Devuelve el estado en el que se encuentra este indicador + * + * @return estado del indicador + */ + public IndicatorState getState(); + + /** + * Devuelve el conjunto de métricas en las que se basa este indicador + * + * @return colección de métricas en las que se basa el indicador + */ + public Collection getMetrics(); + + /** + * Establece el conjunto de métricas en las que se basa este indicador + * + * @param metrics conjunto de métricas en las que se basa el indicador + */ + public void setMetrics(Collection metrics); + + /** + * Establece el estado en el que se encuentra este indicador + * + * @param state, estado del indicador + */ + public void setState(IndicatorState state); + +} diff --git a/src/main/java/us/muit/fs/a4i/model/entities/Metric.java b/src/main/java/us/muit/fs/a4i/model/entities/Metric.java deleted file mode 100644 index 44dfd0c9..00000000 --- a/src/main/java/us/muit/fs/a4i/model/entities/Metric.java +++ /dev/null @@ -1,183 +0,0 @@ -/** - * - */ -package us.muit.fs.a4i.model.entities; - -import java.io.IOException; -import java.time.LocalDateTime; - -import java.time.ZoneOffset; -import java.util.Date; -import java.util.HashMap; -import java.util.logging.Logger; - -import us.muit.fs.a4i.config.Context; -import us.muit.fs.a4i.exceptions.MetricException; - -/** - * @author Isabel Román - * - */ -public class Metric { - private static Logger log=Logger.getLogger(Metric.class.getName()); - /** - * Obligatorio - */ - private String name; - /** - * Obligatorio - */ - private T value; - /** - * Obligatorio - * Fecha en la que se tomó la medida (por defecto cuando se crea el objeto) - */ - private Date date; - - private String description; - private String source; - private String unit; - /** - * Construye un objeto métrica a partir de un constructor, previamente configurado - * Sólo lo utiliza el propio constructor, es privado, nadie, que no sea el constructor, puede crear una métrica - * @param builder Constructor de la métrica - */ - private Metric(MetricBuilder builder){ - - this.description=builder.description; - this.name=builder.name; - this.value=builder.value; - this.source=builder.source; - this.unit=builder.unit; - this.date=builder.date; - } - - /** - * Obtiene la descripción de la métrica - * @return Descripción del significado de la métrica - */ - public String getDescription() { - return description; - } - - - /** - * Consulta el nombre de la métrica - * @return Nombre de la métrica - */ - public String getName() { - return name; - } - /** - * Consulta el valor de la métrica - * @return Medida - */ - public T getValue() { - return value; - } - /** - * Consulta la fuente de información - * @return Origen de la medida - */ - public String getSource() { - return source; - } - /*** - * Establece la fuente de la información para la medida - * @param source fuente de información origen - */ - public void setSource(String source) { - this.source = source; - } - /** - * Consulta las unidades de medida - * @return la unidad usada en la medida - */ - public String getUnit() { - return unit; - } - - /** - * Consulta cuando se obtuvo la métrica - * @return Fecha de consulta de la métrica - */ - public Date getDate() { - return date; - } - - /** - *

Clase para construir métricas. Verifica las métricas antes de crearlas

- * - */ - public static class MetricBuilder{ - private String description; - private String name; - private Date date; - private T value; - private String source; - private String unit; - public MetricBuilder(String metricName, T metricValue) throws MetricException { - HashMap metricDefinition=null; - //el nombre incluye java.lang, si puede eliminar si se manipula la cadena - //hay que quedarse sólo con lo que va detrás del último punto o meter en el fichero el nombre completo - //Pero ¿y si se usan tipos definidos en otras librerías? usar el nombre completo "desambigua" mejor - log.info("Verifico La métrica de nombre "+metricName+" con valor de tipo "+metricValue.getClass().getName()); - try { - metricDefinition=Context.getContext().getChecker().definedMetric(metricName,metricValue.getClass().getName()); - - if(metricDefinition!=null) { - this.name=metricName; - this.value=metricValue; - this.date=Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)); - this.description=metricDefinition.get("description"); - this.unit=metricDefinition.get("unit"); - }else { - throw new MetricException("Métrica "+metricName+" no definida o tipo "+metricValue.getClass().getName()+" incorrecto"); - } - }catch(IOException e) { - throw new MetricException("El fichero de configuración de métricas no se puede abrir"); - } - - - } - /** - *

Establece la descripción de la métrica

- * @param description Breve descripción del significado de la métrica - * @return El propio constructor - */ - public MetricBuilder description(String description){ - this.description=description; - return this; - } - /** - *

Establece la fuente de información

- * @param source Fuente de la que se extrajeron los datos - * @return El propio constructor - */ - public MetricBuilder source(String source){ - this.source=source; - return this; - } - /** - *

Establece las unidades de medida

- * @param unit Unidades de medida de la métrica - * @return El propio constructor - */ - public MetricBuilder unit(String unit){ - this.unit=unit; - return this; - } - public Metric build(){ - return new Metric(this); - } - } - - @Override - public String toString() { - String info; - info="Métrica para "+description+", con valor=" + value + ", source=" + source - + ", unit=" + unit +" fecha de la medida= "+ date; - return info; - } - -} diff --git a/src/main/java/us/muit/fs/a4i/model/entities/Report.java b/src/main/java/us/muit/fs/a4i/model/entities/Report.java index c13a4c65..9b2ae71a 100644 --- a/src/main/java/us/muit/fs/a4i/model/entities/Report.java +++ b/src/main/java/us/muit/fs/a4i/model/entities/Report.java @@ -4,189 +4,183 @@ package us.muit.fs.a4i.model.entities; import java.util.Collection; -import java.util.Date; import java.util.HashMap; -import java.util.List; import java.util.logging.Logger; import us.muit.fs.a4i.control.IndicatorsCalculator; -import us.muit.fs.a4i.exceptions.IndicatorException; /** - *

Aspectos generales de todos los informes

- *

Todos incluirán un conjunto de métricas de tipo numérico y otro de tipo Date

- * @author Isabel Román + *

+ * Aspectos generales de todos los informes + *

+ *

+ * Todos incluyen un conjunto de métricas de tipo numérico y otro de tipo Date + *

+ * + * @author Isabel Román * */ public class Report implements ReportI { - private static Logger log=Logger.getLogger(Report.class.getName()); - /** - *

Identificador unívoco de la entidad a la que se refire el informe en el servidor remoto que se va a utilizar

- */ - private String id; + private static Logger log = Logger.getLogger(Report.class.getName()); /** - *

Los objetos que implementen esta interfaz recurren a calcuadoras con los algoritmos para el cálculo de indicadores

- *

Los algoritmos de cálculo de indicadores serán específicos para un tipo de informe

+ *

+ * Identificador unívoco de la entidad a la que se refire el informe en el + * servidor remoto que se va a utilizar + *

*/ - private IndicatorsCalculator calc; - - - - - private ReportI.Type type=null; + private String entityId; + + private ReportI.ReportType type = null; /** - * Mapa de Métricas + * Mapa de Métricas * - * */ - - private HashMap metrics; - + */ + + private HashMap metrics; + /** * Mapa de indicadores */ - - private HashMap indicators; - - public Report(){ + + private HashMap indicators; + + public Report() { createMaps(); - + } - public Report(String id){ + + public Report(String entityId) { createMaps(); - this.id=id; + this.entityId = entityId; } - public Report(Type type){ + + public Report(ReportType type) { createMaps(); - this.type=type; + this.type = type; } - public Report(Type type,String id){ + + public Report(ReportType type, String entityId) { createMaps(); - this.type=type; - this.id=id; - } + this.type = type; + this.entityId = entityId; + } + private void createMaps() { - metrics=new HashMap(); - indicators=new HashMap(); + metrics = new HashMap(); + indicators = new HashMap(); } + /** - *

Busca la métrica solicita en el informe y la devuelve

- *

Si no existe devuelve null

- * @param name Nombre de la métrica buscada - * @return la métrica localizada + *

+ * Busca la métrica solicita en el informe y la devuelve + *

+ *

+ * Si no existe devuelve null + *

+ * + * @param name Nombre de la métrica buscada + * @return la métrica localizada */ @Override - public Metric getMetricByName(String name) { - log.info("solicitada métrica de nombre "+name); - Metric metric=null; - - if (metrics.containsKey(name)){ - log.info("La métrica está en el informe"); - metric=metrics.get(name); + public ReportItemI getMetricByName(String name) { + log.info("solicitada m�trica de nombre " + name); + ReportItemI metric = null; + + if (metrics.containsKey(name)) { + log.info("La m�trica est� en el informe"); + metric = metrics.get(name); } return metric; } + /** - *

Añade una métrica al informe

+ *

+ * Añade una métrica al informe + *

*/ @Override - public void addMetric(Metric met) { + public void addMetric(ReportItemI met) { metrics.put(met.getName(), met); - log.info("Añadida métrica "+met+" Con nombre "+met.getName()); + log.info("A�adida m�trica " + met + " Con nombre " + met.getName()); } + /** - *

Busca el indicador solicitado en el informe y lo devuelve

- *

Si no existe devuelve null

+ *

+ * Busca el indicador solicitado en el informe y lo devuelve + *

+ *

+ * Si no existe devuelve null + *

+ * * @param name Nombre del indicador buscado * @return el indicador localizado */ @Override - public Indicator getIndicatorByName(String name) { - log.info("solicitado indicador de nombre "+name); - Indicator indicator=null; - - if (indicators.containsKey(name)){ - indicator=indicators.get(name); + public ReportItemI getIndicatorByName(String name) { + log.info("solicitado indicador de nombre " + name); + ReportItemI indicator = null; + + if (indicators.containsKey(name)) { + indicator = indicators.get(name); } return indicator; } -/** - *

Añade un indicador al informe

- * - */ + + /** + *

+ * A�ade un indicador al informe + *

+ * + */ @Override - public void addIndicator(Indicator ind) { - + public void addIndicator(ReportItemI ind) { + indicators.put(ind.getName(), ind); - log.info("Añadido indicador "+ind); + log.info("Añadido indicador " + ind); } + /** - *

Calcula el indicador solicitado y lo incluye en el informe, si se necesita alguna métrica que no exista la calculadora la busca y la incluye

+ *

+ * Calcula el indicador solicitado y lo incluye en el informe, si se necesita + * alguna métrica que no exista la calculadora la busca y la incluye + *

*/ @Override - public void calcIndicator(String name) { - try { - calc.calcIndicator(name, this); - } catch (IndicatorException e) { - log.info("No se puede calcular esta indicador, no se incluirá"); - } + public String getEntityId() { + return entityId; } - @Override - public void setId(String id) { - this.id=id; - } - @Override - public String getId() { - return id; - } - @Override - public void setIndicatorsCalculator(IndicatorsCalculator calc) throws IndicatorException { - log.info("Se establece la calculadora de indicadores que va a usar este informe"); - if(this.type==null) { - this.type=calc.getReportType(); - log.info("El tipo del informe será "+this.type); - }else if(this.type!=calc.getReportType()){ - throw new IndicatorException("La calculadora no concuerda con el tipo de informe"); - } - this.calc=calc; - } - + @Override public String toString() { String repoinfo; - repoinfo="Información del Informe:\n - Métricas: "; - for (String clave:metrics.keySet()) { - repoinfo+="\n Clave: " + clave + metrics.get(clave); + repoinfo = "Información del Informe:\n - Métricas: "; + for (String clave : metrics.keySet()) { + repoinfo += "\n Clave: " + clave + metrics.get(clave); } - repoinfo+="\n - Indicadores: "; - for (String clave:indicators.keySet()) { - repoinfo+="\n Clave: " + clave + indicators.get(clave); + repoinfo += "\n - Indicadores: "; + for (String clave : indicators.keySet()) { + repoinfo += "\n Clave: " + clave + indicators.get(clave); } return repoinfo; } + @Override - public Collection getAllMetrics() { - // TODO Auto-generated method stub + public Collection getAllMetrics() { + return metrics.values(); } + @Override - public ReportI.Type getType() { - return type; - } - @Override - public void setType(ReportI.Type type) { - //Sólo se puede cambiar si no estaba aún establecido - //Un informe no puede cambiar de tipo - if (this.type==null) { - this.type = type; - } + public Collection getAllIndicators() { + + return indicators.values(); } + @Override - public void calcAllIndicators() { - // TODO Auto-generated method stub - + public ReportType getType() { + return type; } - } diff --git a/src/main/java/us/muit/fs/a4i/model/entities/ReportI.java b/src/main/java/us/muit/fs/a4i/model/entities/ReportI.java index 1b152edf..fbbaeb20 100644 --- a/src/main/java/us/muit/fs/a4i/model/entities/ReportI.java +++ b/src/main/java/us/muit/fs/a4i/model/entities/ReportI.java @@ -1,93 +1,94 @@ package us.muit.fs.a4i.model.entities; import java.util.Collection; -import java.util.List; - -import us.muit.fs.a4i.control.IndicatorsCalculator; -import us.muit.fs.a4i.exceptions.IndicatorException; +/** + *

Interfaz para la gestión de informes

+ * @author Isabel Román + * + */ public interface ReportI { /** - *

Tipos de informes, puede necesitarse cuando los algoritmos de cálculo de indicadores difieran según el tipo de informe

- *

Un informe sólo es de un tipo y no se puede modificar una vez establecido

+ *

+ * Tipos de informes, puede necesitarse cuando los algoritmos de cálculo de + * indicadores difieran según el tipo de informe + *

+ *

+ * Un informe sólo es de un tipo y no se puede modificar una vez establecido + *

* */ - public static enum Type{ - REPOSITORY, - DEVELOPER, - PROJECT, - ORGANIZATION - } - - /** - * Consulta una métrica de un informe a partir del nombre - * @param name Nombre de la métrica solicitada - * @return Métrica solicitada - */ - Metric getMetricByName(String name); + public static enum ReportType { + REPOSITORY, DEVELOPER, PROJECT, ORGANIZATION + } + /** - * Obtiene todas las métricas del informe - * @return Colleción de métricas que contiene el informe + * Consulta el tipo del informe + * + * @return tipo del informe */ - Collection getAllMetrics(); - /** - * Añade una métrica al informe - * @param met Nueva métrica - */ - void addMetric(Metric met); + ReportI.ReportType getType(); + /** - * Obtiene un indicador del informe a partir del nombre del mismo - * @param name Nombre del indicador consultado - * @return El indicador + * Obtiene el identificador de la entidad a la que se refiere el informe + * + * @return Identificador unóvoco de la entidad a la que se refiere el informe en + * el remoto */ + String getEntityId(); - Indicator getIndicatorByName(String name); /** - * Añade un indicador al informe - * @param ind Nuevo indicador + * Consulta una métrica de un informe a partir del nombre + * + * @param name Nombre de la métrica solicitada + * @return Métrica solicitada */ + ReportItemI getMetricByName(String name); - void addIndicator(Indicator ind); /** - * Calcula un indicador a partir de su nombre y lo añade al informe - * Si se basa en métricas que no están aún incluidas en el informe las incluye - * @param name Nombre del indicador que se quiere calcular + * Obtiene todas las métricas del informe + * + * @return Colecciónn de métricas que contiene el informe */ - - void calcIndicator(String name); + Collection getAllMetrics(); /** - * Establede el identificador unívoco de la entidad a la que se refiere el informe, debe ser el identificador usado en el remoto - * @param id Identificador unívoco de la entidad a la que se refiere el informe en el remoto + * Añade una métrica al informe + * + * @param metric Nueva métrica */ - void setId(String id); - - /** - * Obtiene el identificador de la entidad a la que se refiere el informe - * @return Identificador unívoco de la entidad a la que se refiere el informe en el remoto - */ - String getId(); + void addMetric(ReportItemI metric); + /** - * Establece la calculadora de indicadores, debe ser específica para el tipo de informe - * @param calc calculadora a utilizar para el cálculo de indicadores - * @throws IndicatorException Si el tipo de la calculadora no coincide con el tipo de informe + * Obtiene un indicador del informe a partir del nombre del mismo + * + * @param indicatorName Nombre del indicador consultado + * @return El indicador */ - void setIndicatorsCalculator(IndicatorsCalculator calc) throws IndicatorException; + + ReportItemI getIndicatorByName(String indicatorName); + /** - * Calcula todos los indicadores especificados por defecto para el tipo de informe y los incluye en el informe - * También incluye las métricas utiizadas + * Obtiene todos los indicadores del informe + * + * @return el conjunto de indicadores del informe */ - void calcAllIndicators(); + Collection getAllIndicators(); + /** - * Establece el tipo del informe, sólo se puede establecer una vez y debe coincidir con la el tipo de la calculadora usada - * @param type Tipo del informe + * Añaade un indicador al informe + * + * @param newIndicator nuevo indicador */ - void setType(ReportI.Type type); + + void addIndicator(ReportItemI newIndicator); + /** - * Obtiene el tipo del informe - * @return Tipo del informe + * Calcula un indicador a partir de su nombre y lo añade al informe Si se basa + * en métricas que no están aún incluidas en el informe las incluye + * + * @param name Nombre del indicador que se quiere calcular */ - ReportI.Type getType(); } \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/model/entities/ReportItem.java b/src/main/java/us/muit/fs/a4i/model/entities/ReportItem.java new file mode 100644 index 00000000..39635410 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/model/entities/ReportItem.java @@ -0,0 +1,296 @@ +/** + * + */ +package us.muit.fs.a4i.model.entities; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.logging.Logger; + +import us.muit.fs.a4i.config.Context; +import us.muit.fs.a4i.exceptions.ReportItemException; + +/** + * @author Isabel Rom�n Entidad para guardar la informaci�n de un indicador o + * una m�trica, elementos de un informe + * + */ +public class ReportItem implements ReportItemI { + private IndicatorI indicator = null; + private static Logger log = Logger.getLogger(ReportItem.class.getName()); + /** + * Nombre del indicador/métrica + */ + private String name; + /** + * Valor del indicador/métrica + */ + private T value; + /** + * Obligatorio Fecha en la que se construye el objeto o se toma la medida + */ + private Date date; + /** + * Descripción del elemento del informe + */ + private String description; + /** + * Origen del elemento + */ + private String source; + /** + * Unidades de medida + */ + private String unit; + + /** + * Construye un objeto ReportItem a partir de un constructor, previamente + * configurado Sólo lo utiliza el propio constructor, es privado, nadie, que no + * sea el constructor, puede crear una ReportItem + * + * @param builder Constructor del ReportItem + */ + private ReportItem(ReportItemBuilder builder) { + + this.description = builder.description; + this.name = builder.name; + this.value = builder.value; + this.source = builder.source; + this.unit = builder.unit; + this.date = builder.date; + } + + /** + * Obtiene la descripción del ReportItem + * + * @return Descripción del significado del ReportItem + */ + public String getDescription() { + return description; + } + + /** + * Consulta el nombre del ReportItem + * + * @return Nombre del ReportItem + */ + public String getName() { + return name; + } + + /** + * Consulta el valor del ReportItem + * + * @return ReportItem + */ + public T getValue() { + return value; + } + + /** + * Consulta la fuente de información + * + * @return Origen del ReportItem + */ + public String getSource() { + return source; + } + + /** + * Consulta las unidades del ReportItem + * + * @return la unidad usada en el ReportItem + */ + public String getUnit() { + return unit; + } + + /** + * Consulta el indicador de ReportItem + * + * @return indicador de ReportItem + */ + public IndicatorI getIndicator() { + return this.indicator; + } + + /** + * Consulta cuando se obtuvo el ReportItem + * + * @return Fecha de consulta del ReportItem + */ + public Date getDate() { + return this.date; + } + + /** + *

+ * Clase para construir ReportItem. Verifica los ReportItem antes de crearlos + *

+ * + */ + public static class ReportItemBuilder { + private String description; + private String name; + private Date date; + private T value; + private String source; + private String unit; + private IndicatorI indicator = null; + + public ReportItemBuilder(String name, T value) throws ReportItemException { + HashMap reportItemDefinition = null; + /** + * Verifico si el elemento está definido y el tipo es correcto + */ + // el nombre incluye java.lang, si puede eliminar si se manipula la cadena + // hay que quedarse sólo con lo que va detrás del último punto o meter en el + // fichero el nombre completo + // Pero ¿y si se usan tipos definidos en otras librerías? usar el nombre + // completo "desambigua" mejor + log.info("Verifico el ReportItem de nombre " + name + " con valor de tipo " + value.getClass().getName()); + try { + // Compruebo si es un indicador + reportItemDefinition = Context.getContext().getChecker().getIndicatorConfiguration() + .definedIndicator(name, value.getClass().getName()); + + if (reportItemDefinition != null) { + this.indicator = new Indicator(); + } else { + // Si no lo era, compruebo si es una métrica + reportItemDefinition = Context.getContext().getChecker().getMetricConfiguration() + .definedMetric(name, value.getClass().getName()); + } + if (reportItemDefinition != null) { + this.name = name; + this.value = value; + this.date = Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)); + this.description = reportItemDefinition.get("description"); + this.unit = reportItemDefinition.get("unit"); + } else { + throw new ReportItemException( + "ReportItem " + name + " no definido o tipo " + value.getClass().getName() + " incorrecto"); + } + } catch (IOException e) { + throw new ReportItemException("El fichero de configuración de ReportItem no se puede abrir"); + } + /* + * Si el ReportItem era un indicador este se ha creado vacío, con el campo state + * a UNDEFINED Si era una métrica el indicador está null Si no era ninguno de + * los dos se ha lanzado una excepción + */ + + } + + /** + *

+ * Establece la descripción del ReportItem + *

+ * + * @param description Breve descripción del significado del ReportItem + * @return El propio constructor + */ + public ReportItemBuilder description(String description) { + this.description = description; + return this; + } + + /** + *

+ * Establece la fecha del ReportItem + *

+ * + * @param date Fecha del ReportItem + * @return El propio constructor + */ + public ReportItemBuilder date(Date date) { + this.date = date; + return this; + } + + /** + *

+ * Establece el estado del ReportItem si era un indicador + *

+ * + * @param state Estado del indicador + * @return El propio constructor + * @throws ReportItemException intenta establecer datos de tipo indicador en una + * métrica + */ + public ReportItemBuilder indicator(IndicatorI.IndicatorState state) throws ReportItemException { + if (this.indicator != null) { + this.indicator.setState(state); + } else { + throw new ReportItemException("El Report Item no es un indicador, no puede contener estado"); + } + + return this; + } + + /** + *

+ * Establece el conjunto de métricas si el ReportItem es un indicador + *

+ * + * @param metrics + * @return El propio constructor + * @throws ReportItemException + */ + public ReportItemBuilder metrics(Collection metrics) throws ReportItemException { + if (this.indicator != null) { + this.indicator.setMetrics(metrics); + } else { + throw new ReportItemException("El Report Item no es un indicador, no puede contener más metricas"); + } + return this; + } + + /** + *

+ * Establece la fuente de información + *

+ * + * @param source Fuente de la que se extrajeron los datos + * @return El propio constructor + */ + public ReportItemBuilder source(String source) { + this.source = source; + return this; + } + + /** + *

+ * Establece las unidades de medida + *

+ * + * @param unit Unidades de medida del ReportItem + * @return El propio constructor + */ + public ReportItemBuilder unit(String unit) { + this.unit = unit; + return this; + } + + public ReportItem build() { + return new ReportItem(this); + } + } + + @Override + public String toString() { + String info = "ReportItem "; + if (this.indicator != null) { + info = "De tipo Indicador "; + } + info = info + "para " + description + ", con valor=" + value + ", source=" + source + ", unit=" + unit + + " fecha de la medida= " + date; + + return info; + } + +} diff --git a/src/main/java/us/muit/fs/a4i/model/entities/ReportItemI.java b/src/main/java/us/muit/fs/a4i/model/entities/ReportItemI.java new file mode 100644 index 00000000..96601451 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/model/entities/ReportItemI.java @@ -0,0 +1,56 @@ +package us.muit.fs.a4i.model.entities; + +import java.util.Date; + +public interface ReportItemI { + + /** + * Consulta el nombre de la métrica + * + * @return Nombre de la métrica + */ + public String getName(); + + /** + * Consulta el valor de la métrica + * + * @return Medida + */ + public T getValue(); + + /** + * Consulta cuando se obtuvo la métrica + * + * @return Fecha de consulta de la métrica + */ + public Date getDate(); + + /** + * Obtiene la descripción de la métrica + * + * @return Descripción del significado de la métrica + */ + public String getDescription(); + + /** + * Consulta la fuente de información + * + * @return Origen de la medida + */ + public String getSource(); + + /** + * Consulta las unidades de medida + * + * @return la unidad usada en la medida + */ + public String getUnit(); + + /** + * Consulta el indicador + * + * @return el indicador + */ + public IndicatorI getIndicator(); + +} \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/model/entities/doc-files/entitiesPackage.GIF b/src/main/java/us/muit/fs/a4i/model/entities/doc-files/entitiesPackage.GIF deleted file mode 100644 index 4ea1fd86..00000000 Binary files a/src/main/java/us/muit/fs/a4i/model/entities/doc-files/entitiesPackage.GIF and /dev/null differ diff --git a/src/main/java/us/muit/fs/a4i/model/entities/doc-files/entitiesPackage.gif b/src/main/java/us/muit/fs/a4i/model/entities/doc-files/entitiesPackage.gif new file mode 100644 index 00000000..214f3c8a Binary files /dev/null and b/src/main/java/us/muit/fs/a4i/model/entities/doc-files/entitiesPackage.gif differ diff --git a/src/main/java/us/muit/fs/a4i/model/entities/package-info.java b/src/main/java/us/muit/fs/a4i/model/entities/package-info.java index d39ec279..2ee672c3 100644 --- a/src/main/java/us/muit/fs/a4i/model/entities/package-info.java +++ b/src/main/java/us/muit/fs/a4i/model/entities/package-info.java @@ -1,9 +1,14 @@ /** - *

Este paquete contiene las clases de tipo entidad, que representan la información manejada

- * Paquete de entidades + *

+ * Este paquete contiene las clases de tipo entidad, que representan la + * información manejada + *

+ * Paquete de entidades * - * @author Isabel Román - * @version 0.0 + * @author Isabel Román + * @version V.0.2 */ package us.muit.fs.a4i.model.entities; \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/model/remote/GitHubDeveloperEnquirer.java b/src/main/java/us/muit/fs/a4i/model/remote/GitHubDeveloperEnquirer.java new file mode 100644 index 00000000..7b1aed2e --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/model/remote/GitHubDeveloperEnquirer.java @@ -0,0 +1,131 @@ +/** + * + */ +package us.muit.fs.a4i.model.remote; + +import java.util.List; +import java.util.logging.Logger; + +import org.kohsuke.github.GHEvent; +import org.kohsuke.github.GHEventInfo; +import org.kohsuke.github.GHEventPayload; +import org.kohsuke.github.GHProject; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHUser; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.PagedIterable; + +import us.muit.fs.a4i.exceptions.MetricException; +import us.muit.fs.a4i.model.entities.ReportI; +import us.muit.fs.a4i.model.entities.ReportItem; + +import us.muit.fs.a4i.model.entities.ReportItem.ReportItemBuilder; + +/** + * Deuda técnica Esta clase debe consultar datos sobre un desarrollador concreto + * de github Ahora mismo está en estado lamentable Simplemente busca los eventos + * de un desarrollador No localiza eventos de tipo ISSUE, que son los que se + * quería RECUERDA: las métricas tienen que estar incluidas en el fichero de + * configuración a4iDefault.json + */ +public class GitHubDeveloperEnquirer extends GitHubEnquirer { + public GitHubDeveloperEnquirer() { + super(); + myQueries.put("closedIssuesLastMonth",GitHubDeveloperEnquirer::getClosedIssuesLastMonth); + myQueries.put("assignedIssuesLastMonth",GitHubDeveloperEnquirer::getAssignedIssuesLastMonth); + log.info("Incluidos nombres metricas en Enquirer"); + } + + private static Logger log = Logger.getLogger(GitHubDeveloperEnquirer.class.getName()); + /** + *

+ * Identificador unívoco de la entidad a la que se refire el informe en el + * servidor remoto que se va a utilizar + *

+ */ + private String entityId; + + @Override + public ReportI buildReport(String developerId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ReportItem getMetric(String metricName, String developerId) throws MetricException { + GHUser developer; + + GitHub gb = getConnection(); + try { + developer = gb.getUser(developerId); + log.info("Localizado el desarrollador " + developer.getName()); + } catch (Exception e) { + e.printStackTrace(); + throw new MetricException("No se puede acceder al desarrollador " + developerId + " para recuperarlo"); + } + + return getMetric(metricName, developer); + } + + private ReportItem getMetric(String metricName, GHUser developer) throws MetricException { + log.info("Localizando la metrica " + metricName); + ReportItem metric; + if (developer == null) { + throw new MetricException( + "Intenta obtener una métrica de desarrollador sin haber obtenido el desarrollador"); + } + /* + switch (metricName) { + case "closedIssuesLastMonth": + metric = getClosedIssuesLastMonth(developer); + break; + case "assignedIssuesLastMonth": + metric = getAssignedIssuesLastMonth(developer); + break; + default: + throw new MetricException("La métrica " + metricName + " no está definida para un repositorio"); + } + */ + + return myQueries.get(metricName).apply(developer); + } + + static private ReportItem getClosedIssuesLastMonth(GHUser developer) { + log.info("Consultando los issues asignados a un desarrollador"); + ReportItemBuilder builder = null; + int issues = 0; + + try { + PagedIterable events = developer.listEvents(); + for (GHEventInfo event : events) { + log.info("Evento tipo" + event.getType() + " en la fecha " + event.getCreatedAt()); + if (event.getType() == GHEvent.ISSUES) { + + GHEventPayload.Issue payload = event.getPayload(GHEventPayload.Issue.class); + log.info(payload.getAction()); + issues++; + + } + + } + builder = new ReportItem.ReportItemBuilder("closedIssuesLastMonth", issues); + builder.source("GitHub"); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + static private ReportItem getAssignedIssuesLastMonth(GHUser developer) { + // TODO Auto-generated method stub + return null; + } + + @Override + public RemoteType getRemoteType() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/us/muit/fs/a4i/model/remote/GitHubEnquirer.java b/src/main/java/us/muit/fs/a4i/model/remote/GitHubEnquirer.java index fedad8d6..fc624a38 100644 --- a/src/main/java/us/muit/fs/a4i/model/remote/GitHubEnquirer.java +++ b/src/main/java/us/muit/fs/a4i/model/remote/GitHubEnquirer.java @@ -4,60 +4,103 @@ package us.muit.fs.a4i.model.remote; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.logging.Logger; +import java.util.function.*; +import java.util.Map; +import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; import org.kohsuke.github.GitHubBuilder; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.remote.RemoteEnquirer.RemoteType; + /** - *

Clase abstracta con los métodos comunes a los constructores que recogen la información del servicio GitHub

- *

Para las consultas a github se recurre a la API github-API

- *

Actualmente sólo incluye el establecimiento del identificador de entidad y la obtención del objeto GitHub para las consultas

- * @author Isabel Román + *

+ * Clase abstracta con los métodos comunes a los constructores que consultan la + * información del servicio GitHub. Usan Github como backend + *

+ *

+ * Para las consultas a github se recurre a la API github-API + *

+ *

+ * Actualmente sólo incluye el establecimiento del identificador de entidad y la + * obtención del objeto GitHub para las consultas. + *

+ * protected Map> myQueries; será un mapa de + * funciones, en el que la clave es el nombre de la métrica y el valor es la + * referencia a la función que permite recuperar esa métrica + *

+ *

+ * + * @author Isabel Román + * @param * */ -public abstract class GitHubEnquirer implements RemoteEnquirer { - private static Logger log=Logger.getLogger(GitHubEnquirer.class.getName()); - protected List metricNames; - +public abstract class GitHubEnquirer implements RemoteEnquirer { + private static Logger log = Logger.getLogger(GitHubEnquirer.class.getName()); + protected Map> myQueries; + private RemoteEnquirer.RemoteType type = RemoteEnquirer.RemoteType.GITHUB; /** - *

Referencia al objeto GitHub que permite hacer consultas al servidor Github

- *

Se crea al invocar por primera vez getConnectiony se mantiene mientras el GHBuilder esté vivo

+ *

+ * Referencia al objeto GitHub que permite hacer consultas al servidor Github + *

+ *

+ * Se crea al invocar por primera vez getConnectiony se mantiene mientras el + * GHBuilder está vivo + *

*/ - private GitHub github=null; - - - + private GitHub github = null; + public GitHubEnquirer() { - metricNames=new ArrayList(); + myQueries = new HashMap>(); } - - + /** - *

El objeto para contectarse al GitHub se crea la primera vez que se invoca getConnection

+ *

+ * El objeto para contectarse al GitHub se crea la primera vez que se invoca + * getConnection + *

+ * * @return devuelve un objeto GitHub que permite la consulta al remoto */ protected GitHub getConnection() { - - if(github==null) try { - - log.info("Creando el objeto GitHub"); - github = GitHubBuilder.fromEnvironment().build(); - - - }catch(Exception e) { - log.info(e+" No se puede crear la instancia GitHub\n"); - log.info("Recuerde que debe configurar las variables de entorno GITHUB_LOGIN y GITHUB_OAUTH con su nombre de usuario y token respectivamente"); - } + + if (github == null) + try { + github = GitHubBuilder.fromEnvironment().build(); + log.info("Creado el objeto GitHub"); + + } catch (Exception e) { + log.info(e + " No se puede crear la instancia GitHub\n"); + log.info( + "Recuerde que debe configurar las variables de entorno GITHUB_LOGIN y GITHUB_OAUTH con su nombre de usuario y token respectivamente"); + e.printStackTrace(); + } return github; } - protected void setMetric(String newMetric) { - metricNames.add(newMetric); + + /** + * Permite añadir una nueva función de búsqueda de métrica al mapa + * + * @param newMetric Nombre de la métrica nueva + * @param functionPointer Referencia a la función que implementa el algoritmo + * para recuperar la métrica + */ + protected void setMetric(String newMetric, Function functionPointer) { + myQueries.put(newMetric, functionPointer); } - public List getAvailableMetrics(){ - return metricNames; + + public List getAvailableMetrics() { + List metrics = new ArrayList(myQueries.keySet()); + return metrics; + } + + public RemoteType getRemoteType() { + return type; } } diff --git a/src/main/java/us/muit/fs/a4i/model/remote/GitHubOrganizationEnquirer.java b/src/main/java/us/muit/fs/a4i/model/remote/GitHubOrganizationEnquirer.java new file mode 100644 index 00000000..2cb833b7 --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/model/remote/GitHubOrganizationEnquirer.java @@ -0,0 +1,422 @@ +/** + * + */ +package us.muit.fs.a4i.model.remote; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import org.kohsuke.github.GHIssueState; +import org.kohsuke.github.GHOrganization; + +import org.kohsuke.github.GitHub; +import org.kohsuke.github.PagedIterable; +import org.kohsuke.github.GHProject; +import org.kohsuke.github.GHRepository; + +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; +import us.muit.fs.a4i.model.entities.ReportItem.ReportItemBuilder; + +/** + *

+ * Esta clase permite consultar métricas sobre una organización GitHub + *

+ *

+ * Deuda técnica: sería necesario verificar mejor el funcionamiento de las + * consultas de proyectos cerrados y abiertos, no parece hacer lo esperado + * Habría que incluir más métricas y algún indicador RECUERDA: las métricas + * tienen que estar incluidas en el fichero de configuración a4iDefault.json + *

+ * + * @author Isabel Román + * + */ + +public class GitHubOrganizationEnquirer extends GitHubEnquirer { + private static Logger log = Logger.getLogger(GitHubOrganizationEnquirer.class.getName()); + /** + *

+ * Identificador unívoco de la entidad a la que se refire el informe en el + * servidor remoto que se va a utilizar + *

+ */ + private String entityId; + + public GitHubOrganizationEnquirer() { + super(); + myQueries.put("repositoriesWithOpenPullRequest",GitHubOrganizationEnquirer::getRepositoriesWithOpenPullRequest); + myQueries.put("repositories",GitHubOrganizationEnquirer::getRepositories); + myQueries.put("pullRequests",GitHubOrganizationEnquirer::getPullRequests); + myQueries.put("members",GitHubOrganizationEnquirer::getMembers); + myQueries.put("teams",GitHubOrganizationEnquirer::getTeams); + myQueries.put("openProjects",GitHubOrganizationEnquirer::getOpenProjects); + myQueries.put("closedProjects",GitHubOrganizationEnquirer::getClosedProjects); + myQueries.put("followers",GitHubOrganizationEnquirer::getFollowers); + + //Equipo 7 23/24 + myQueries.put("teamsBalance",GitHubOrganizationEnquirer::getTeamsBalance); + + log.info("Incluidas métricas en Enquirer"); + } + + @Override + public ReportI buildReport(String organizationId) { + ReportI report = null; + log.info("Invocado el metodo que construye un informe de organización, para la organizacion " + organizationId); + /** + *

+ * Información sobre la organizacion de GitHub + *

+ */ + GHOrganization organization; + /** + *

+ * En estos momentos cada vez que se invoca construyeObjeto se crea y rellena + * uno nuevo + *

+ *

+ * Deuda técnica: se puede optimizar consultando sólo las diferencias respecto a + * la fecha de la última representación local + *

+ */ + + try { + log.info("Nombre organizacion = " + organizationId); + + GitHub gb = getConnection(); + organization = gb.getOrganization(organizationId); + + log.info("La organizacion es de la empresa " + organization.getCompany() + " fue creada en " + + organization.getCreatedAt() + " se puede contactar en " + organization.getEmail()); + log.info("leidos datos de la " + organization); + report = new Report(organizationId); + + /** + * Métricas directas de tipo conteo + */ + + report.addMetric(getMembers(organization)); + log.info("Incluida metrica members "); + + report.addMetric(getTeams(organization)); + log.info("Incluida metrica teams "); + + report.addMetric(getFollowers(organization)); + log.info("Incluida metrica followers "); + + report.addMetric(getPullRequests(organization)); + log.info("Incluida metrica pullRequests "); + + report.addMetric(getRepositories(organization)); + log.info("Incluida metrica repositories "); + + report.addMetric(getRepositoriesWithOpenPullRequest(organization)); + log.info("Incluida metrica repositoriesWithPullRequest "); + + report.addMetric(getOpenProjects(organization)); + log.info("Incluida metrica openProjects "); + + report.addMetric(getClosedProjects(organization)); + log.info("Incluida metrica closedProjects "); + + } catch (Exception e) { + log.severe("Problemas en la conexión " + e); + } + + return report; + } + + /** + * Permite consultar desde fuera una única métrica de la organización con el id + * que se pase como parámetro + */ + @Override + public ReportItem getMetric(String metricName, String organizationId) throws MetricException { + log.info("Invocado getMetric para buscar " + metricName); + GHOrganization organization; + + GitHub gb = getConnection(); + try { + organization = gb.getOrganization(organizationId); + } catch (Exception e) { + e.printStackTrace(); + throw new MetricException( + "No se puede acceder a la organizacion remota " + organizationId + " para recuperarla"); + } + + return getMetric(metricName, organization); + } + + /** + *

+ * Crea la métrica solicitada consultando la organizacion que se pasa como + * parámetro + *

+ * + * @param metricName Métrica solicitada + * @param organization Organizacion + * @return La métrica creada + * @throws MetricException Si la métrica no está definida se lanzará una + * excepción + */ + private ReportItem getMetric(String metricName, GHOrganization organization) throws MetricException { + ReportItem metric = null; + if (organization == null) { + throw new MetricException("Intenta obtener una métrica sin haber obtenido los datos de la organizacion"); + } + switch (metricName) { + case "repositoriesWithOpenPullRequest": + metric = getRepositoriesWithOpenPullRequest(organization); + break; + case "repositories": + metric = getRepositories(organization); + break; + case "pullRequests": + metric = getPullRequests(organization); + break; + case "members": + metric = getMembers(organization); + break; + case "teams": + metric = getTeams(organization); + break; + case "openProjects": + metric = getOpenProjects(organization); + break; + case "closedProjects": + metric = getClosedProjects(organization); + break; + case "followers": + metric = getFollowers(organization); + break; + case "teamsBalance": + metric=getTeamsBalance(organization); + break; + default: + throw new MetricException("La métrica " + metricName + " no está definida para un repositorio"); + } + + return metric; + } + + static private ReportItem getRepositoriesWithOpenPullRequest(GHOrganization organization) { + log.info("Consultando los repositorios con pull requests abiertos"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("repositoriesWithOpenPullRequest", + organization.getRepositoriesWithOpenPullRequests().size()); + builder.source("GitHub"); + } catch (ReportItemException | IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + static private ReportItem getRepositories(GHOrganization organization) { + log.info("Consultando los repositorios"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("repositories", organization.getPublicRepoCount()); + builder.source("GitHub"); + } catch (ReportItemException | IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + static private ReportItem getMembers(GHOrganization organization) { + log.info("Consultando los miembros"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("members", organization.listMembers().toList().size()); + builder.source("GitHub"); + } catch (ReportItemException | IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + static private ReportItem getTeams(GHOrganization organization) { + log.info("Consultando los equipos"); + ReportItemBuilder builder = null; + try { + int size = organization.getTeams().size(); + log.info("Numero de equipos" + size); + builder = new ReportItem.ReportItemBuilder("teams", organization.getTeams().size()); + builder.source("GitHub"); + } catch (ReportItemException | IOException e) { + log.fine("unable to retry teams"); + e.printStackTrace(); + } + return builder.build(); + } + + static private ReportItem getFollowers(GHOrganization organization) { + log.info("Consultando los seguidores"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("followers", organization.getFollowersCount()); + builder.source("GitHub"); + } catch (ReportItemException | IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + static private ReportItem getPullRequests(GHOrganization organization) { + log.info("Consultando los pull requests"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("pullRequests", organization.getPullRequests().size()); + builder.source("GitHub"); + } catch (ReportItemException | IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + static private ReportItem getOpenProjects(GHOrganization organization) { + + ReportItemBuilder builder = null; + try { + log.info("Consultando los proyectos abiertos en " + organization.getUrl()); + int number = 0; + // first we look for projects associated with the organization + PagedIterable pagina = organization.listProjects(GHProject.ProjectStateFilter.OPEN); + List proyectos = pagina.toList(); + number = number + proyectos.size(); + // second we look for projects associated with the repos + PagedIterable repositories = organization.listRepositories(); + + for (GHRepository repo : repositories) { + PagedIterable repoproyects = repo.listProjects(GHProject.ProjectStateFilter.OPEN); + number = number + repoproyects.toList().size(); + } + + log.info("Open projects " + number); + builder = new ReportItem.ReportItemBuilder("openProjects", number); + + for (GHProject pro : proyectos) { + log.info("Proyecto " + pro.getName() + " en estado " + pro.getState()); + } + builder.source("GitHub"); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + static private ReportItem getClosedProjects(GHOrganization organization) { + ReportItemBuilder builder = null; + try { + log.info("Consultando los proyectos cerrados en " + organization.getUrl()); + int number = 0; + // first we look for projects associated with the organization + PagedIterable pagina = organization.listProjects(GHProject.ProjectStateFilter.CLOSED); + List proyectos = pagina.toList(); + number = number + proyectos.size(); + // second we look for projects associated with the repos + PagedIterable repositories = organization.listRepositories(); + + for (GHRepository repo : repositories) { + PagedIterable repoproyects = repo.listProjects(GHProject.ProjectStateFilter.CLOSED); + number = number + repoproyects.toList().size(); + } + + log.info("Closed projects " + number); + builder = new ReportItem.ReportItemBuilder("closedProjects", number); + + for (GHProject pro : proyectos) { + log.info("Proyecto " + pro.getName() + " en estado " + pro.getState()); + } + builder.source("GitHub"); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + + } + static private Map getIssuesPerRepository(GHOrganization organization) { + log.info("Consultando los issues de cada uno de los repositorios de la organización"); + Map mapa = new HashMap<>(); + try { + + PagedIterable repositorios = organization.listRepositories(); + for (GHRepository repo : repositorios) { + mapa.put(repo,repo.getIssues(GHIssueState.OPEN).size()); + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return mapa; + } + + static private Map getTeamsPerRepository(GHOrganization organization) { + log.info("Consultando el número de equipos por repositorio"); + ReportItemBuilder> builder=null; + Map mapa = new HashMap<>(); + try { + PagedIterable repositorios = organization.listRepositories(); + for (GHRepository repo : repositorios) { + mapa.put(repo,repo.getTeams().size()); + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return mapa; + } + + static private ReportItem getTeamsBalance(GHOrganization organization) { + + log.info("Consultando el equilibrio entre equipos e issues de los repositorios"); + Integer issuesTotales= getIssuesPerRepository(organization).size(); + Integer desarrolladoresTotales=(Integer) getTeams(organization).getValue(); + Integer numProjects = (Integer) getRepositories(organization).getValue(); + Map issuesPerProject =getIssuesPerRepository(organization); + Map teamsPerProject = getTeamsPerRepository(organization); + + Double desajustePromedioOrganizacion=0.0; + List desajustePromedioProyecto = new ArrayList(); + + //Es raro que sea Integer y en la declaración de a4iDefault esta(ba) como Double + ReportItemBuilder builder=null; + try { + //Calcular el desajuste promedio de cada proyecto y guardarlo en desajustePromedioProyecto + issuesPerProject.forEach((repo,numIssues)-> + desajustePromedioProyecto.add((double) (Math.abs(numIssues/(issuesTotales)-(teamsPerProject.get(repo)/desarrolladoresTotales)))) + ); + + //Sumar todos los desajustes y guardarlos en desajustePromedioOrganizacion + for (Double desajuste: desajustePromedioProyecto){ + desajustePromedioOrganizacion+=desajuste; + } + + builder = new ReportItem.ReportItemBuilder("teamsBalance", (int) Math.round(desajustePromedioOrganizacion)); + builder.source("GitHub"); + } catch (ReportItemException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + +} diff --git a/src/main/java/us/muit/fs/a4i/model/remote/GitHubRepositoryEnquirer.java b/src/main/java/us/muit/fs/a4i/model/remote/GitHubRepositoryEnquirer.java index 3e52feec..a93333e0 100644 --- a/src/main/java/us/muit/fs/a4i/model/remote/GitHubRepositoryEnquirer.java +++ b/src/main/java/us/muit/fs/a4i/model/remote/GitHubRepositoryEnquirer.java @@ -4,151 +4,184 @@ package us.muit.fs.a4i.model.remote; import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Calendar; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.function.Function; import java.util.logging.Logger; +import java.util.stream.Collectors; +import org.kohsuke.github.GHBranch; +import org.kohsuke.github.GHCommit; +import org.kohsuke.github.GHIssue; +import org.kohsuke.github.GHIssueState; +import org.kohsuke.github.GHPullRequest; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GHRepositoryStatistics; -import org.kohsuke.github.GitHub; import org.kohsuke.github.GHRepositoryStatistics.CodeFrequency; +import org.kohsuke.github.GHUser; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.PagedIterable; +import us.muit.fs.a4i.config.GitFlow; import us.muit.fs.a4i.exceptions.MetricException; - -import us.muit.fs.a4i.model.entities.Metric; -import us.muit.fs.a4i.model.entities.Metric.MetricBuilder; +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; +import us.muit.fs.a4i.model.entities.ReportItem.ReportItemBuilder; /** - * @author Isabel Román - * + * @author Isabel Román y alumnos de factoría software del MUIT de la ETSI (US) + * deuda técnica: las métricas tras la etiqueta //equipo 3 tienen problemas, + * no están acordes al indicador para el que fueron creadas RECUERDA: + * las métricas tienen que estar incluidas en el fichero de + * configuración a4iDefault.json + * + * TODO: esto está en periodo de limpieza, vamos a utilizar un mapa de punteros a funciones para quitarnos el case de enmedio, y ya que estamos + * podemos eliminar la lista de métricas y usar las keys del mapa (Esto falta) + * Debería haber un constructor para esto, para que la creación del mapa esté fuera + * */ -public class GitHubRepositoryEnquirer extends GitHubEnquirer { +public class GitHubRepositoryEnquirer extends GitHubEnquirer { + /** + * para trazar el código + */ private static Logger log = Logger.getLogger(GitHubRepositoryEnquirer.class.getName()); + /** - *

Constructor

+ *

+ * Constructor + *

*/ public GitHubRepositoryEnquirer() { super(); - metricNames.add("subscribers"); - metricNames.add("forks"); - metricNames.add("watchers"); + //para cada métrica se añade en el mapa la función que la consulta y como clave el nombre de la métrica + myQueries.put("subscribers", GitHubRepositoryEnquirer::getSubscribers); + myQueries.put("forks", GitHubRepositoryEnquirer::getForks); + myQueries.put("watchers", GitHubRepositoryEnquirer::getWatchers); + myQueries.put("starts", GitHubRepositoryEnquirer::getStars); + myQueries.put("issues", GitHubRepositoryEnquirer::getIssues); + myQueries.put("closedIssues", GitHubRepositoryEnquirer::getClosedIssues); + myQueries.put("openIssues", GitHubRepositoryEnquirer::getOpenIssues); + myQueries.put("creation", GitHubRepositoryEnquirer::getCreation); + myQueries.put("lastUpdated", GitHubRepositoryEnquirer::getLastUpdated); + myQueries.put("lastPush", GitHubRepositoryEnquirer::getLastPush); + myQueries.put("totalAdditions", GitHubRepositoryEnquirer::getTotalAdditions); + + myQueries.put("totalDeletions", GitHubRepositoryEnquirer::getTotalDeletions); + myQueries.put("collaborators", GitHubRepositoryEnquirer::getCollaborators); + myQueries.put("ownerCommits", GitHubRepositoryEnquirer::getOwnerCommits); + + // equipo3 + myQueries.put("issuesLastMonth", GitHubRepositoryEnquirer::getIssuesLastMonth); + myQueries.put("closedIssuesLastMonth", GitHubRepositoryEnquirer::getClosedIssuesLastMonth); + myQueries.put("meanClosedIssuesLastMonth", GitHubRepositoryEnquirer::getMeanClosedIssuesLastMonth); + myQueries.put("issues4DevLastMonth", GitHubRepositoryEnquirer::getIssues4DevLastMonth); + + // equipo 4 + myQueries.put("totalPullReq", GitHubRepositoryEnquirer::getTotalPullReq); + myQueries.put("closedPullReq", GitHubRepositoryEnquirer::getClosedPullReq); + + // equipo 5 + myQueries.put("PRAcceptedLastYear", GitHubRepositoryEnquirer::getPRAcceptedLastYear); + myQueries.put("PRAcceptedLastMonth", GitHubRepositoryEnquirer::getPRAcceptedLastMonth); + myQueries.put("PRRejectedLastYear", GitHubRepositoryEnquirer::getPRRejectedLastYear); + myQueries.put("PRRejectedLastMonth", GitHubRepositoryEnquirer::getPRRejectedLastMonth); + + // Equipo 1 + myQueries.put("conventionalCommits", GitHubRepositoryEnquirer::getConventionalCommits); + myQueries.put("commitsWithDescription", GitHubRepositoryEnquirer::getCommitsWithDescription); + myQueries.put("issuesWithLabels", GitHubRepositoryEnquirer::getIssuesWithLabels); + myQueries.put("gitFlowBranches", GitHubRepositoryEnquirer::getGitFlowBranches); + myQueries.put("conventionalPullRequests", GitHubRepositoryEnquirer::getConventionalPullRequests); + + log.info("Añadidas métricas al GHRepositoryEnquirer"); } + /** + * Devuelve el informe para el repositorio cuyo id se pasa como parámetro + */ @Override public ReportI buildReport(String repositoryId) { - ReportI myRepo = null; - log.info("Invocado el método que construye un objeto RepositoryReport"); + ReportI report = null; + log.info("Invocado el método que construye un objeto RepositoryReport"); /** *

- * Información sobre el repositorio obtenida de GitHub + * Información sobre el repositorio obtenida de GitHub *

*/ - GHRepository remoteRepo; + GHRepository repo; /** *

* En estos momentos cada vez que se invoca construyeObjeto se crea y rellena * uno nuevo *

*

- * Deuda técnica: se puede optimizar consultando sólo las diferencias respecto a - * la fecha de la última representación local + * Deuda técnica: se puede optimizar consultando sólo las diferencias respecto a + * la fecha de la última representación local *

*/ try { log.info("Nombre repo = " + repositoryId); - +/* GitHub gb = getConnection(); - remoteRepo = gb.getRepository(repositoryId); - log.info("leído " + remoteRepo); - myRepo = new Report(repositoryId); - - /** - * Métricas directas de tipo conteo - */ + repo = gb.getRepository(repositoryId); + log.info("El repositorio es de " + repo.getOwnerName() + " Y su descripción es " + repo.getDescription()); + log.info("leído " + repo); + */ + report = new Report(repositoryId); - MetricBuilder subscribers = new Metric.MetricBuilder("subscribers", - remoteRepo.getSubscribersCount()); - subscribers.source("GitHub"); - myRepo.addMetric(subscribers.build()); - log.info("Añadida métrica suscriptores " + subscribers); - - MetricBuilder forks = new Metric.MetricBuilder("forks", remoteRepo.getForksCount()); - forks.source("GitHub"); - myRepo.addMetric(forks.build()); - log.info("Añadida métrica forks " + forks); - - MetricBuilder watchers = new Metric.MetricBuilder("watchers", - remoteRepo.getWatchersCount()); - watchers.source("GitHub"); - myRepo.addMetric(watchers.build()); - - MetricBuilder stars = new Metric.MetricBuilder("stars", remoteRepo.getStargazersCount()); - stars.source("GitHub"); - myRepo.addMetric(stars.build()); - - MetricBuilder issues = new Metric.MetricBuilder("issues", remoteRepo.getOpenIssueCount()); - issues.source("GitHub"); - myRepo.addMetric(issues.build()); /** - * Métricas directas de tipo fecha + * Métricas más elaboradas, requieren más "esfuerzo" */ - MetricBuilder creation = new Metric.MetricBuilder("creation", remoteRepo.getCreatedAt()); - creation.source("GitHub"); - myRepo.addMetric(creation.build()); + report.addMetric(getMetric("totalAdditions", repositoryId)); + log.info("Incluida metrica totalAdditions "); - MetricBuilder push = new Metric.MetricBuilder("lastPush", remoteRepo.getPushedAt()); - push.description("Último push realizado en el repositorio").source("GitHub"); - myRepo.addMetric(push.build()); + report.addMetric(getMetric("totalDeletions", repositoryId)); + log.info("Incluida metrica totalDeletions "); - MetricBuilder updated = new Metric.MetricBuilder("lastUpdated", remoteRepo.getUpdatedAt()); - push.description("Última actualización").source("GitHub"); - myRepo.addMetric(updated.build()); /** - * Métricas más elaboradas, requieren más "esfuerzo" + * Métricas directas de tipo conteo */ - GHRepositoryStatistics data = remoteRepo.getStatistics(); - List codeFreq = data.getCodeFrequency(); - int additions = 0; - int deletions = 0; - for (CodeFrequency freq : codeFreq) { - - if ((freq.getAdditions() != 0) || (freq.getDeletions() != 0)) { - Date fecha = new Date((long) freq.getWeekTimestamp() * 1000); - log.info("Fecha modificaciones " + fecha); - additions += freq.getAdditions(); - deletions += freq.getDeletions(); - } - - } - MetricBuilder totalAdditions = new Metric.MetricBuilder("totalAdditions", additions); - totalAdditions.source("GitHub, calculada") - .description("Suma el total de adiciones desde que el repositorio se creó"); - myRepo.addMetric(totalAdditions.build()); + report.addMetric(getMetric("subscribers", repositoryId)); + log.info("Incluida metrica suscribers "); - MetricBuilder totalDeletions = new Metric.MetricBuilder("totalDeletions", deletions); - totalDeletions.source("GitHub, calculada") - .description("Suma el total de borrados desde que el repositorio se creó"); - myRepo.addMetric(totalDeletions.build()); + + /** + * Métricas directas de tipo fecha + */ + report.addMetric(getMetric("creation", repositoryId)); + log.info("Incluida metrica creation "); + report.addMetric(getMetric("lastPush", repositoryId)); + log.info("Incluida metrica lastPush "); + } catch (Exception e) { - log.severe("Problemas en la conexión " + e); + log.severe("Problemas en la conexión " + e); } - return myRepo; + return report; } /** - * Permite consultar desde fuera una métrica del repositorio indicado + * Permite consultar desde fuera una única métrica del repositorio indicado + * + * @param metricName el nombre de la métrica + * @param repositoryId el id del repositorio + * @return el item para incluir en el informe del repositorio */ @Override - public Metric getMetric(String metricName, String repositoryId) throws MetricException { + public ReportItem getMetric(String metricName, String repositoryId) throws MetricException { GHRepository remoteRepo; GitHub gb = getConnection(); @@ -162,56 +195,54 @@ public Metric getMetric(String metricName, String repositoryId) throws MetricExc return getMetric(metricName, remoteRepo); } -/** - *

Crea la métrica solicitada consultando el repositorio remoto que se pasa como parámetro

- * @param metricName Métrica solicitada - * @param remoteRepo Repositorio remoto - * @return La métrica creada - * @throws MetricException Si la métrica no está definida se lanzará una excepción - */ - private Metric getMetric(String metricName, GHRepository remoteRepo) throws MetricException { - Metric metric; + + /** + *

+ * Crea la métrica solicitada consultando el repositorio remoto que se pasa como + * parámetro + *

+ * + * @param metricName Métrica solicitada + * @param remoteRepo Repositorio remoto + * @return La métrica creada + * @throws MetricException Si la métrica no está definida se lanzará una + * excepción + */ + private ReportItem getMetric(String metricName, GHRepository remoteRepo) throws MetricException { + ReportItem metric; if (remoteRepo == null) { - throw new MetricException("Intenta obtener una métrica sin haber obtenido los datos del repositorio"); - } - switch (metricName) { - case "totalAdditions": - metric = getTotalAdditions(remoteRepo); - break; - case "totalDeletions": - metric = getTotalDeletions(remoteRepo); - break; - default: - throw new MetricException("La métrica " + metricName + " no está definida para un repositorio"); + throw new MetricException("Intenta obtener una métrica sin haber obtenido los datos del repositorio"); } - + metric=myQueries.get(metricName).apply(remoteRepo); return metric; } /* - * A partir de aquí los algoritmos específicoso para hacer las consultas de cada - * métrica + * A partir de aquí los algoritmos específicos para hacer las consultas de cada + * métrica */ /** *

- * Obtención del número total de adiciones al repositorio + * Obtención del número total de adiciones al repositorio *

* * @param remoteRepo el repositorio remoto sobre el que consultar - * @return la métrica con el número total de adiciones desde el inicio - * @throws MetricException Intenta crear una métrica no definida + * @return la métrica con el número total de adiciones desde el inicio + * @throws MetricException Intenta crear una métrica no definida */ - private Metric getTotalAdditions(GHRepository remoteRepo) throws MetricException { - Metric metric = null; + static private ReportItem getTotalAdditions(GHRepository remoteRepo) throws MetricException { + log.fine("buscando totaladditions"); + ReportItem metric = null; GHRepositoryStatistics data = remoteRepo.getStatistics(); + log.fine("estadisticas "+data.toString()); List codeFreq; try { codeFreq = data.getCodeFrequency(); int additions = 0; - + for (CodeFrequency freq : codeFreq) { if (freq.getAdditions() != 0) { @@ -221,13 +252,14 @@ private Metric getTotalAdditions(GHRepository remoteRepo) throws MetricException } } - MetricBuilder totalAdditions = new Metric.MetricBuilder("totalAdditions", additions); - totalAdditions.source("GitHub, calculada") - .description("Suma el total de adiciones desde que el repositorio se creó"); - metric = totalAdditions.build(); + ReportItemBuilder builder = new ReportItem.ReportItemBuilder("totalAdditions", additions); + builder.source("GitHub, calculada") + .description("Suma el total de adiciones desde que el repositorio se creó"); + metric = builder.build(); - } catch (IOException e) { - // TODO Auto-generated catch block + } catch (Exception e) { + + log.warning("Problemas al leer codefrequency en getTotalAdditions"); e.printStackTrace(); } return metric; @@ -236,15 +268,15 @@ private Metric getTotalAdditions(GHRepository remoteRepo) throws MetricException /** *

- * Obtención del número total de eliminaciones del repositorio + * Obtención del número total de eliminaciones del repositorio *

* * @param remoteRepo el repositorio remoto sobre el que consultar - * @return la métrica con el número total de eliminaciones desde el inicio - * @throws MetricException Intenta crear una métrica no definida + * @return la métrica con el n�mero total de eliminaciones desde el inicio + * @throws MetricException Intenta crear una métrica no definida */ - private Metric getTotalDeletions(GHRepository remoteRepo) throws MetricException { - Metric metric = null; + static private ReportItem getTotalDeletions(GHRepository remoteRepo) throws MetricException { + ReportItem metric = null; GHRepositoryStatistics data = remoteRepo.getStatistics(); List codeFreq; @@ -262,17 +294,894 @@ private Metric getTotalDeletions(GHRepository remoteRepo) throws MetricException } } - MetricBuilder totalDeletions = new Metric.MetricBuilder("totalDeletions", deletions); + ReportItemBuilder totalDeletions = new ReportItem.ReportItemBuilder("totalDeletions", + deletions); totalDeletions.source("GitHub, calculada") - .description("Suma el total de eliminaciones desde que el repositorio se creó"); + .description("Suma el total de eliminaciones desde que el repositorio se cre�"); metric = totalDeletions.build(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); + } catch (ReportItemException e) { + // TODO Auto-generated catch block + e.printStackTrace(); } return metric; } + /** + * Devuelve el número de suscriptores + * + * @param repo repositorio que se consulta + * @return item para el informe + */ + static private ReportItem getSubscribers(GHRepository repo) { + log.info("Consultando los subscriptores"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("subscribers", repo.getSubscribersCount()); + builder.source("GitHub"); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + /** + * Devuelve el número de forks + * + * @param repo repositorio que se consulta + * @return item para el informe + */ + static private ReportItem getForks(GHRepository repo) { + log.info("Consultando los forks"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("forks", repo.getForksCount()); + builder.source("GitHub"); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + /** + * Devuelve los usuarios que observan el repositorio + * + * @param repo repositorio que se consulta + * @return item para el informe + */ + static private ReportItem getWatchers(GHRepository repo) { + log.info("Consultando los watchers"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("watchers", repo.getWatchersCount()); + builder.source("GitHub"); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + /** + * Devuelve el número de estrellas + * + * @param repo repositorio que se consulta + * @return item para el informe + */ + static private ReportItem getStars(GHRepository repo) { + log.info("Consultando las starts"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("stars", repo.getStargazersCount()); + builder.description("Numero de estrellas").source("GitHub"); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + /** + * Devuelve el número de commits que realiza el responsable del repositorio + * + * @param repo repositorio que se consulta + * @return item para el informe + */ + static private ReportItem getOwnerCommits(GHRepository repo) { + log.info("Consultando los commits del responsable del repositorio"); + ReportItemBuilder builder = null; + GHRepositoryStatistics data = repo.getStatistics(); + + try { + builder = new ReportItem.ReportItemBuilder("ownerCommits", + data.getParticipation().getOwnerCommits().size()); + + builder.description("Commits del responsable").source("GitHub"); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + /** + * Devuelve el número de tickets + * + * @param repo repositorio que se consulta + * @return item para el informe + */ + static private ReportItem getIssues(GHRepository repo) { + log.info("Consultando los issues"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("issues", repo.getIssues(GHIssueState.ALL).size()); + builder.description("Numero de asuntos totales").source("GitHub"); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + /** + * Devuelve los issues abiertos en el repositorio que se pasa como parámetro + * + * @param repo repositorio que se consulta + * @return item para incluir en el informe + */ + static private ReportItem getOpenIssues(GHRepository repo) { + log.info("Consultando los issues abiertos"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("openIssues", repo.getOpenIssueCount()); + builder.description("Numero de asuntos abiertos").source("GitHub"); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + /** + * Devuelve los issues cerrados del repositorio + * + * @param repo repositorio que se consulta + * @return item para incluir en el informe + */ + static private ReportItem getClosedIssues(GHRepository repo) { + log.info("Consultando los issues cerrados"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("closedIssues", + repo.getIssues(GHIssueState.CLOSED).size()); + builder.description("Numero de asuntos cerrados").source("GitHub"); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + /** + * Devuelve el número de colaboradores + * + * @param repo repositorio que se consulta + * @return item para el informe + */ + static private ReportItem getCollaborators(GHRepository repo) { + log.info("Consultando los colaboradores"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("collaborators", repo.getCollaborators().size()); + builder.description("Numero de colaboradores en el repositorio").source("GitHub"); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + /** + * Devuelve la fecha de creación del repositorio + * + * @param repo + * @return item para el informe + */ + static private ReportItem getCreation(GHRepository repo) { + log.info("Consultando fecha de creación"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("creation", repo.getCreatedAt()); + builder.description("Fecha de creación del repositorio").source("GitHub"); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + /** + * Devuelve la fecha del último push en el repositorio que se pase como + * argumento + * + * @param repo repositorio que se consulta + * @return item para el informe + */ + static private ReportItem getLastPush(GHRepository repo) { + log.info("Consultando el ultimo push"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("lastPush", repo.getPushedAt()); + builder.description("Último push realizado en el repositorio").source("GitHub"); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + /** + * Fecha de la última actualización + * + * @param repo repositorio que se consulta + * @return item para incluir en el informe del repositorio + */ + static private ReportItem getLastUpdated(GHRepository repo) { + log.info("Consultando la ultima actualización"); + ReportItemBuilder builder = null; + try { + builder = new ReportItem.ReportItemBuilder("lastUpdated", repo.getUpdatedAt()); + builder.description("Última actualización").source("GitHub"); + + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder.build(); + } + + // Métodos añadidos por el equipo 3 + /** + * + * @param remoteRepo + * @return ReportItem with the number of issues created last month + * @throws MetricException + */ + static private ReportItem getIssuesLastMonth(GHRepository remoteRepo) throws MetricException { + log.info("Consultando los issues abiertos un mes"); + int issuesLastMonth = 0; + ReportItemBuilder builder = null; + try { + // Calcular la fecha de hace un mes + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.MONTH, -1); + Date lastMonth = cal.getTime(); + + // Obtener todas las issues creadas desde la fecha de hace un mes + PagedIterable issues = remoteRepo.queryIssues().state(GHIssueState.ALL).since(lastMonth).list(); + + // Contar el número de issues + for (GHIssue issue : issues) { + if (issue.getCreatedAt().after(lastMonth)) { + issuesLastMonth++; + log.finer("issue manejado por " + issue.getUser().getName()); + + } + } + + } catch (IOException e) { + throw new MetricException( + "Error al obtener el número de issues creadas en el último mes: " + e.getMessage()); + } + + // Construir y devolver la métrica + try { + builder = new ReportItem.ReportItemBuilder("issuesLastMonth", issuesLastMonth); + builder.description("Issues creadas en el mes").source("GitHub"); + + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return builder.build(); + } + + /** + * + * @param remoteRepo + * @return ReportItem with the issues closed last month + * @throws MetricException + */ + static private ReportItem getClosedIssuesLastMonth(GHRepository remoteRepo) throws MetricException { + int closedIssuesLastMonth = 0; + ReportItemBuilder builder = null; + try { + // Calcular la fecha de hace un mes + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.MONTH, -1); + Date lastMonth = cal.getTime(); + + // Obtener todas las issues cerradas desde la fecha de hace un mes + PagedIterable issues = remoteRepo.queryIssues().state(GHIssueState.CLOSED).since(lastMonth).list(); + + // Contar el número de issues cerradas + for (GHIssue issue : issues) { + if (issue.getClosedAt() != null && issue.getClosedAt().after(lastMonth)) { + closedIssuesLastMonth++; + } + } + + } catch (Exception e) { + throw new MetricException( + "Error al obtener el número de issues cerradas en el último mes: " + e.getMessage()); + } + + // Construir y devolver la métrica + try { + builder = new ReportItem.ReportItemBuilder("closedIssuesLastMonth", closedIssuesLastMonth); + builder.description("Issues cerradas en el mes").source("GitHub"); + + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return builder.build(); + } + + /** + * + * @param remoteRepo + * @return ReportItem with the average of closed issues per member in a month + * @throws MetricException + */ + static private ReportItem getMeanClosedIssuesLastMonth(GHRepository remoteRepo) throws MetricException { + int closedIssuesLastMonth = 0; + int activeMembers = 0; + Map issuesClosedByMember = new HashMap<>(); + ReportItemBuilder builder = null; + + try { + // Calcular la fecha de hace un mes + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.MONTH, -1); + Date lastMonth = cal.getTime(); + + // Obtener todas las issues cerradas desde la fecha de hace un mes + PagedIterable issues = remoteRepo.queryIssues().state(GHIssueState.CLOSED).since(lastMonth).list(); + + // Contar el número de issues cerradas por cada miembro + for (GHIssue issue : issues) { + if (issue.getClosedAt() != null && issue.getClosedAt().after(lastMonth)) { + GHUser closer = issue.getClosedBy(); + if (closer != null) { + issuesClosedByMember.put(closer.getLogin(), + issuesClosedByMember.getOrDefault(closer.getLogin(), 0) + 1); + closedIssuesLastMonth++; + } + } + } + + // Contar el número de miembros activos + activeMembers = issuesClosedByMember.size(); + + } catch (IOException e) { + throw new MetricException( + "Error al obtener el promedio de issues cerradas por miembro activo en el último mes: " + + e.getMessage()); + } + + // Calcular el promedio de issues que cierra un miembro + double avgClosedIssuesPerMember = activeMembers > 0 ? (double) closedIssuesLastMonth / activeMembers : 0.0; + + // Construir y devolver la métrica + + try { + builder = new ReportItem.ReportItemBuilder("meanClosedIssuesLastMonth", avgClosedIssuesPerMember); + builder.description("Average of closed issues per member").source("GitHub"); + + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return builder.build(); + } + + /** + * + * @param remoteRepo + * @return ReportItem with a map indicating the issues assigned to each member + * in a month + * @throws MetricException + * + * Deuda técnica: esto no está bien porque aquí este + * reportitem es un mapa y luego lo tratan como si fuera + * un double... no es coherente una parte con la otra. + */ + static private ReportItem getIssues4DevLastMonth(GHRepository remoteRepo) throws MetricException { + Map issuesAssignedByMember = new HashMap<>(); + ReportItemBuilder> builder = null; + + try { + // Calcular la fecha de hace un mes + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.MONTH, -1); + Date lastMonth = cal.getTime(); + + // Obtener todas las issues creadas desde la fecha de hace un mes + PagedIterable issues = remoteRepo.queryIssues().state(GHIssueState.ALL).since(lastMonth).list(); + + // Contar el número de issues asignadas a cada miembro + for (GHIssue issue : issues) { + if (issue.getCreatedAt().after(lastMonth) && !issue.getAssignees().isEmpty()) { + for (GHUser assignee : issue.getAssignees()) { + issuesAssignedByMember.put(assignee.getLogin(), + issuesAssignedByMember.getOrDefault(assignee.getLogin(), 0) + 1); + } + } + } + + } catch (IOException e) { + throw new MetricException("Error al obtener el número de issues asignadas a cada miembro en el último mes: " + + e.getMessage()); + } + + // Construir y devolver la métrica + try { + builder = new ReportItem.ReportItemBuilder>("issues4DevLastMonth", + issuesAssignedByMember); + builder.description("Issues assigned to a developer last month").source("GitHub"); + + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return builder.build(); + } + + static private ReportItem getClosedPullReq(GHRepository repo) { + log.info("Consultando los pull requests completados"); + ReportItemBuilder builder = null; + + int completedPullRequests = 0; + + try { + + for (GHPullRequest pullRequest : repo.getPullRequests(GHIssueState.CLOSED)) { + + completedPullRequests++; + + } + builder = new ReportItem.ReportItemBuilder("closedPullReq", completedPullRequests); + builder.description("Número de pull requests completados").source("GitHub"); + } catch (Exception e) { + e.printStackTrace(); + } + return builder.build(); + } + + static private ReportItem getTotalPullReq(GHRepository repo) { + log.info("Consultando los pull requests totales"); + ReportItemBuilder builder = null; + + int totalPullRequests = 0; + + try { + + for (GHPullRequest pullRequest : repo.getPullRequests(GHIssueState.ALL)) { + + totalPullRequests++; + + } + builder = new ReportItem.ReportItemBuilder("totalPullReq", totalPullRequests); + builder.description("Número de pull requests totales").source("GitHub"); + } catch (Exception e) { + e.printStackTrace(); + } + return builder.build(); + } + + // Equipo 5 + + /** + *

+ * Filtra las solicitudes de extracción (pull requests) según la fecha de + * creación y el estado de aceptación. + *

+ * + * @param pullRequests la lista de solicitudes de extracción a filtrar + * @param startDate la fecha de inicio del intervalo de tiempo para filtrar + * @param endDate la fecha de finalización del intervalo de tiempo para + * filtrar + * @param accepted indica si se deben filtrar las solicitudes de extracción + * aceptadas (true) o rechazadas (false) + * @return la lista de solicitudes de extracción que cumplen con los criterios + * de filtrado + */ + private static List filterPullRequests(List pullRequests, LocalDateTime startDate, + LocalDateTime endDate, boolean accepted) { + return pullRequests.stream().filter(pr -> { + try { + LocalDateTime createdDate = pr.getCreatedAt().toInstant().atZone(ZoneId.systemDefault()) + .toLocalDateTime(); + return createdDate.isAfter(startDate) && createdDate.isBefore(endDate) + && (accepted ? pr.isMerged() : !pr.isMerged()); + } catch (IOException e) { + log.warning("Failed to get creation date for PR #" + pr.getNumber() + "\n" + e); + return false; + } + }).collect(Collectors.toList()); + } + + /** + *

+ * Obtención del número de solicitudes de extracción aceptadas en el último año. + *

+ * + * @param remoteRepo el repositorio remoto sobre el que consultar + * @return la métrica con el número de solicitudes de extracción aceptadas en el + * último año + * @throws MetricException si ocurre un error al obtener las solicitudes de + * extracción + */ + static private ReportItem getPRAcceptedLastYear(GHRepository remoteRepo) throws MetricException { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime oneYearAgo = now.minusYears(1); + + try { + List pullRequests = remoteRepo.getPullRequests(GHIssueState.CLOSED); + List acceptedLastYear = filterPullRequests(pullRequests, oneYearAgo, now, true); + + ReportItemBuilder acceptedLastYearMetric = new ReportItem.ReportItemBuilder<>("PRAcceptedLastYear", + acceptedLastYear.size()); + acceptedLastYearMetric.source("GitHub, calculada") + .description("Número de solicitudes de extracción aceptadas en el último año"); + + return acceptedLastYearMetric.build(); + } catch (IOException | ReportItemException e) { + throw new MetricException( + "Error al obtener las solicitudes de extracción aceptadas en el último año\n" + e); + } + } + + /** + *

+ * Obtención del número de solicitudes de extracción aceptadas en el último mes. + *

+ * + * @param remoteRepo el repositorio remoto sobre el que consultar + * @return la métrica con el número de solicitudes de extracción aceptadas en el + * último mes + * @throws MetricException si ocurre un error al obtener las solicitudes de + * extracción + */ + static private ReportItem getPRAcceptedLastMonth(GHRepository remoteRepo) throws MetricException { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime oneMonthAgo = now.minusMonths(1); + + try { + List pullRequests = remoteRepo.getPullRequests(GHIssueState.CLOSED); + List acceptedLastMonth = filterPullRequests(pullRequests, oneMonthAgo, now, true); + + ReportItemBuilder acceptedLastMonthMetric = new ReportItem.ReportItemBuilder<>( + "PRAcceptedLastMonth", acceptedLastMonth.size()); + acceptedLastMonthMetric.source("GitHub, calculada") + .description("Número de solicitudes de extracción aceptadas en el último mes"); + + return acceptedLastMonthMetric.build(); + } catch (IOException | ReportItemException e) { + throw new MetricException( + "Error al obtener las solicitudes de extracción aceptadas en el último mes\n" + e); + } + } + + /** + *

+ * Obtención del número de solicitudes de extracción rechazadas en el último + * año. + *

+ * + * @param remoteRepo el repositorio remoto sobre el que consultar + * @return la métrica con el número de solicitudes de extracción rechazadas en + * el último año + * @throws MetricException si ocurre un error al obtener las solicitudes de + * extracción + */ + static private ReportItem getPRRejectedLastYear(GHRepository remoteRepo) throws MetricException { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime oneYearAgo = now.minusYears(1); + + try { + List pullRequests = remoteRepo.getPullRequests(GHIssueState.CLOSED); + List rejectedLastYear = filterPullRequests(pullRequests, oneYearAgo, now, false); + + ReportItemBuilder rejectedLastYearMetric = new ReportItem.ReportItemBuilder<>("PRRejectedLastYear", + rejectedLastYear.size()); + rejectedLastYearMetric.source("GitHub, calculada") + .description("Número de solicitudes de extracción rechazadas en el último año"); + + return rejectedLastYearMetric.build(); + } catch (IOException | ReportItemException e) { + throw new MetricException( + "Error al obtener las solicitudes de extracción rechazadas en el último año\n" + e); + } + } + + /** + *

+ * Obtención del número de solicitudes de extracción rechazadas en el último + * mes. + *

+ * + * @param remoteRepo el repositorio remoto sobre el que consultar + * @return la métrica con el número de solicitudes de extracción rechazadas en + * el último mes + * @throws MetricException si ocurre un error al obtener las solicitudes de + * extracción + */ + static private ReportItem getPRRejectedLastMonth(GHRepository remoteRepo) throws MetricException { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime oneMonthAgo = now.minusMonths(1); + + try { + List pullRequests = remoteRepo.getPullRequests(GHIssueState.CLOSED); + List rejectedLastMonth = filterPullRequests(pullRequests, oneMonthAgo, now, false); + + ReportItemBuilder rejectedLastMonthMetric = new ReportItem.ReportItemBuilder<>( + "PRRejectedLastMonth", rejectedLastMonth.size()); + rejectedLastMonthMetric.source("GitHub, calculada") + .description("Número de solicitudes de extracción rechazadas en el último mes"); + + return rejectedLastMonthMetric.build(); + } catch (IOException | ReportItemException e) { + throw new MetricException( + "Error al obtener las solicitudes de extracción rechazadas en el último mes\n" + e); + } + } + + // Metricas equipo 1 curso 23/24 + /** + *

+ * Obtiene el ratio de commits convencionales en el último mes + *

+ * + * @param remoteRepo Repositorio remoto + * @return La métrica con el ratio de commits convencionales + * @throws MetricException Si se produce un error al consultar los commits o al + * crear la métrica + */ + static private ReportItem getConventionalCommits(GHRepository remoteRepo) throws MetricException { + // Attributes + ReportItem metric = null; + List commits; + + // Logic + // Query the commits in the last month to check if they are conventional + try { + commits = remoteRepo.queryCommits().since(new Date(System.currentTimeMillis() - 30 * 24 * 60 * 60 * 1000)) + .list().toList(); + + // Calculate the ratio of conventional commits + Double conventionalRatio; + if (commits.size() == 0) { + conventionalRatio = 0.0; + } else { + int conventionalCommits = 0; + for (GHCommit commit : commits) { + if (commit.getCommitShortInfo().getMessage().matches( + "^(revert: )?(feat|fix|docs|style|refactor|perf|test|chore)(\\(.+\\))?: .{1,50}")) { + conventionalCommits++; + } + } + conventionalRatio = (double) conventionalCommits / commits.size(); + } + + // Create the metric + ReportItemBuilder conventionalCommitsMetric = new ReportItem.ReportItemBuilder( + "conventionalCommits", conventionalRatio); + conventionalCommitsMetric.source("GitHub, calculada") + .description("Número de commits convencionales en el último mes"); + metric = conventionalCommitsMetric.build(); + } catch (IOException e) { + throw new MetricException("Error al consultar los commits del repositorio"); + } catch (ReportItemException e) { + throw new MetricException("Error al crear la métrica"); + } + return metric; + } + + /** + *

+ * Obtiene el ratio de commits con descripción en el último mes + *

+ * + * @param remoteRepo Repositorio remoto + * @return La métrica con el ratio de commits con descripción + * @throws MetricException Si se produce un error al consultar los commits o al + * crear la métrica + */ + static private ReportItem getCommitsWithDescription(GHRepository remoteRepo) throws MetricException { + // Attributes + ReportItem metric = null; + List commits; + + // Logic + // Query the commits in the last month to check if they have a description + try { + commits = remoteRepo.queryCommits().since(new Date(System.currentTimeMillis() - 30 * 24 * 60 * 60 * 1000)) + .list().toList(); + + // Calculate the ratio of commits with description + Double commitsWithDescriptionRatio; + if (commits.size() == 0) { + commitsWithDescriptionRatio = 0.0; + } else { + int commitsWithDescription = 0; + for (GHCommit commit : commits) { + if (commit.getCommitShortInfo().getMessage().matches(".*\\n\\n.*")) { + commitsWithDescription++; + } + } + commitsWithDescriptionRatio = (double) commitsWithDescription / commits.size(); + } + + // Create the metric + ReportItemBuilder commitsWithDescriptionMetric = new ReportItem.ReportItemBuilder( + "commitsWithDescription", commitsWithDescriptionRatio); + commitsWithDescriptionMetric.source("GitHub, calculada") + .description("Número de commits con descripción en el último mes"); + metric = commitsWithDescriptionMetric.build(); + } catch (IOException e) { + throw new MetricException("Error al consultar los commits del repositorio"); + } catch (ReportItemException e) { + throw new MetricException("Error al crear la métrica"); + } + return metric; + } + + /** + *

+ * Obtiene el ratio de issues con etiquetas en el repositorio + *

+ * + * @param remoteRepo Repositorio remoto + * @return La métrica con el ratio de issues con etiquetas + * @throws MetricException Si se produce un error al consultar los issues o al + * crear la métrica + */ + static private ReportItem getIssuesWithLabels(GHRepository remoteRepo) throws MetricException { + // Attributes + ReportItem metric = null; + List issues; + + // Logic + // Query the open issues to check if they have labels + try { + issues = remoteRepo.getIssues(GHIssueState.OPEN); + + // Calculate the ratio of issues with labels + // By default, the ratio is 1.0 (100%) if there are no issues + Double issuesWithLabelsRatio = 1.0; + + if (issues.size() > 0) { + int issuesWithLabels = issues.stream().filter(issue -> issue.getLabels().size() > 0).toList().size(); + issuesWithLabelsRatio = (double) issuesWithLabels / issues.size(); + } + + ReportItemBuilder issuesWithLabelsMetric = new ReportItem.ReportItemBuilder( + "issuesWithLabels", issuesWithLabelsRatio); + issuesWithLabelsMetric.source("GitHub, calculada") + .description("Número de issues con etiquetas en el repositorio"); + metric = issuesWithLabelsMetric.build(); + } catch (IOException e) { + throw new MetricException("Error al consultar los issues del repositorio"); + } catch (ReportItemException e) { + throw new MetricException("Error al crear la métrica"); + } + return metric; + } + + /** + *

+ * Obtiene el ratio de ramas que siguen las convenciones de Git Flow en el + * repositorio + *

+ * + * @param remoteRepo Repositorio remoto + * @return La métrica con el ratio de ramas que siguen las convenciones de Git + * Flow + * @throws MetricException Si se produce un error al consultar las ramas o al + * crear la métrica + */ + static private ReportItem getGitFlowBranches(GHRepository remoteRepo) throws MetricException { + // Attributes + ReportItem metric = null; + List branches; + + // Logic + // Query the branches to check if they follow the Git Flow naming conventions + try { + branches = remoteRepo.getBranches().values().stream().toList(); + + // Calculate the ratio of Git Flow branches + Double gitFlowBranchesRatio; + if (branches.size() == 0) { + gitFlowBranchesRatio = 0.0; + } else { + gitFlowBranchesRatio = (double) branches.stream() + .filter(branch -> GitFlow.isGitFlowBranch(branch.getName())).toList().size() / branches.size(); + } + + // Create the metric + ReportItemBuilder gitFlowBranchesMetric = new ReportItem.ReportItemBuilder( + "gitFlowBranches", gitFlowBranchesRatio); + gitFlowBranchesMetric.source("GitHub, calculada") + .description("Número de ramas que siguen las convenciones de Git Flow en el repositorio"); + metric = gitFlowBranchesMetric.build(); + } catch (IOException e) { + throw new MetricException("Error al consultar las ramas del repositorio"); + } catch (ReportItemException e) { + throw new MetricException("Error al crear la métrica"); + } + return metric; + } + + /** + *

+ * Obtiene el ratio de pull requests convencionales en el último mes + *

+ * + * @param remoteRepo Repositorio remoto + * @return La métrica con el ratio de pull requests convencionales + * @throws MetricException Si se produce un error al consultar los pull requests + * o al crear la métrica + */ + static private ReportItem getConventionalPullRequests(GHRepository remoteRepo) throws MetricException { + ReportItem metric = null; + List pullRequests; + + // Logic + // Query the pull requests in the last month to check if they are conventional + try { + pullRequests = remoteRepo.queryPullRequests().state(GHIssueState.OPEN).list().toList(); + + // Calculate the ratio of conventional pull requests + Double conventionalPullRequestsRatio; + if (pullRequests.size() == 0) { + conventionalPullRequestsRatio = 0.0; + } else { + int conventionalPullRequests = 0; + for (GHPullRequest pullRequest : pullRequests) { + if (pullRequest.getTitle().matches( + "^(revert: )?(feat|fix|docs|style|refactor|perf|test|chore)(\\(.+\\))?: .{1,50}")) { + conventionalPullRequests++; + } + } + conventionalPullRequestsRatio = (double) conventionalPullRequests / pullRequests.size(); + } + + // Create the metric + ReportItemBuilder conventionalPullRequestsMetric = new ReportItem.ReportItemBuilder( + "conventionalPullRequests", conventionalPullRequestsRatio); + conventionalPullRequestsMetric.source("GitHub, calculada") + .description("Número de pull requests convencionales en el último mes"); + metric = conventionalPullRequestsMetric.build(); + } catch (IOException e) { + throw new MetricException("Error al consultar los pull requests del repositorio"); + } catch (ReportItemException e) { + throw new MetricException("Error al crear la métrica"); + } + + return metric; + } + } diff --git a/src/main/java/us/muit/fs/a4i/model/remote/IEFRemoteEnquirer.java b/src/main/java/us/muit/fs/a4i/model/remote/IEFRemoteEnquirer.java new file mode 100644 index 00000000..b0a79e8c --- /dev/null +++ b/src/main/java/us/muit/fs/a4i/model/remote/IEFRemoteEnquirer.java @@ -0,0 +1,150 @@ +package us.muit.fs.a4i.model.remote; + +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; +import us.muit.fs.a4i.model.entities.ReportItemI; +import org.kohsuke.github.*; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.List; + +/** + * RemoteEnquirer para extraer las métricas Kanban–Scrum de un repositorio GitHub. + * + * Métricas: + * 1) cycleTime – Tiempo medio (h) desde que una tarea está en "Sprint Backlog/Pendiente" + * (punto de partida) hasta su cierre. + * 2) waitTime – Tiempo medio (h) que una tarea permanece en estados previos a "En progreso". + * 3) throughput – Número de tareas cerradas en la última semana (tareas/semana). + * 4) WIP – Promedio de tareas abiertas en las cuatro etapas activas del tablero. + */ +public class IEFRemoteEnquirer implements RemoteEnquirer { + + private final GitHub github; + + private ReportItemI safeBuildReportItem(String metricName, Object value) { + try { + // Creamos el builder con el tipo correcto + ReportItem.ReportItemBuilder builder = new ReportItem.ReportItemBuilder<>(metricName, value); + return builder.build(); + } catch (ReportItemException e) { + throw new RuntimeException("Error creando ReportItem", e); + } + } + public IEFRemoteEnquirer(GitHub github) { + this.github = github; + } + + @Override + public ReportI buildReport(String entityId) { + // Construye un informe añadiendo cada métrica soportada + ReportI report = new Report(); + for (String metric : getAvailableMetrics()) { + try { + report.addMetric(getMetric(metric, entityId)); + } catch (MetricException e) { + System.err.println("Error al añadir la métrica '" + metric + "': " + e.getMessage()); + } + } + return report; + } + + @Override + public ReportItemI getMetric(String metricName, String entityId) throws MetricException { + try { + GHRepository repo = github.getRepository(entityId); + Instant now = Instant.now(); + Instant oneWeekAgo = now.minus(Duration.ofDays(7)); + + switch (metricName) { + case "throughput": + case "waitTime": + case "cycleTime": { + // Listado de todas las issues cerradas + PagedIterable closedIssues = repo.listIssues(GHIssueState.CLOSED); + int count = 0; + double totalWait = 0, totalCycle = 0; + + for (GHIssue issue : closedIssues.toList()) { + Instant closedAt = issue.getClosedAt().toInstant(); + // Sólo consideramos las cerradas durante la última semana para throughput + if (closedAt.isBefore(oneWeekAgo)) continue; + count++; + + // Fecha de creación: corresponde a entrada en Sprint Backlog/Pendiente + Instant createdAt = issue.getCreatedAt().toInstant(); + + // Calcular waitTime: desde creación hasta etiquetado "En progreso" + Instant enterProgress = createdAt; + for (GHIssueEvent ev : issue.listEvents().toList()) { + if ("labeled".equals(ev.getEvent()) + && "En progreso".equals(ev.getLabel().getName())) { + enterProgress = ev.getCreatedAt().toInstant(); + break; + } + } + totalWait += Duration.between(createdAt, enterProgress).toHours(); + + // Calcular cycleTime: ahora es desde creación (Sprint Backlog) hasta cierre + totalCycle += Duration.between(createdAt, closedAt).toHours(); + } +double value; + if ("throughput".equals(metricName)) { + // throughput = número de issues cerradas en la última semana + value = count; + } else if ("waitTime".equals(metricName)) { + // waitTime = tiempo medio en espera antes de entrar en progreso + value = count > 0 ? totalWait / count : 0; + } else { + // cycleTime = tiempo medio desde backlog (creación) hasta cierre + value = count > 0 ? totalCycle / count : 0; + } + return safeBuildReportItem(metricName, value); + + } + + case "WIP": { + // WIP = promedio de tareas abiertas en las cuatro etapas activas + PagedIterable openIssues = repo.listIssues(GHIssueState.OPEN); + int backlog=0, inProgress=0, reviewPend=0, inReview=0; + + for (GHIssue issue : openIssues.toList()) { + for (GHLabel lbl : issue.getLabels()) { + switch (lbl.getName()) { + case "Sprint Backlog/Pendiente": backlog++; break; + case "En progreso": inProgress++; break; + case "Pendiente de Revisión": reviewPend++; break; + case "En revisión": inReview++; break; + } + } + } + double wipValue = (backlog + inProgress + reviewPend + inReview) / 4.0; + return safeBuildReportItem("WIP", wipValue); + } + + default: + throw new MetricException("Métrica no soportada: " + metricName); + } + + } catch (IOException e) { + throw new MetricException("Error consultando GitHub: " + e.getMessage()); + + } + } + + @Override + public List getAvailableMetrics() { + // Definimos las métricas que este RemoteEnquirer puede consultar + return List.of("cycleTime", "waitTime", "throughput", "WIP"); + } + + @Override + public RemoteType getRemoteType() { + return RemoteType.GITHUB; + } +} diff --git a/src/main/java/us/muit/fs/a4i/model/remote/RemoteEnquirer.java b/src/main/java/us/muit/fs/a4i/model/remote/RemoteEnquirer.java index b380d2f8..36587b3b 100644 --- a/src/main/java/us/muit/fs/a4i/model/remote/RemoteEnquirer.java +++ b/src/main/java/us/muit/fs/a4i/model/remote/RemoteEnquirer.java @@ -6,38 +6,75 @@ import java.util.List; import us.muit.fs.a4i.exceptions.MetricException; - -import us.muit.fs.a4i.model.entities.Metric; import us.muit.fs.a4i.model.entities.ReportI; +import us.muit.fs.a4i.model.entities.ReportItemI; /** - *

Interfaz para desacoplar el mecanismo de obtención de métricas del servidor remoto que se use como fuente de las mismas

- *

Un conjunto de métricas es específico para un tipo de entidad a informar: organización, proyecto, repositorio, desarrollador...

- *

La identidad se refiere al identificador unívoco de la entidad sobre la que se quiere informar en el servidor remoto, la semántica puede depender del tipo de entidad y del remoto

- * @author IsabelRomán + *

+ * Interfaz para desacoplar el mecanismo de obtención de métricas del servidor + * remoto que se use como fuente de las mismas + *

+ *

+ * Un conjunto de métricas es específico para un tipo de entidad a informar: + * organización, proyecto, repositorio, desarrollador... + *

+ *

+ * La identidad se refiere al identificador unívoco de la entidad sobre la que + * se quiere informar en el servidor remoto, la semántica puede depender del + * tipo de entidad y del remoto + *

+ * + * @author Isabel Román * */ -public interface RemoteEnquirer{ - +public interface RemoteEnquirer { + /** + * Hasta el momento sólo se ha desarrollado la conexión con github como backend + */ + public static enum RemoteType { + GITHUB + } + /** - *

Construye el informe sobre la entidad indicada con las métricas por defecto

- * @param entityId Identificador unívoco en el remoto de la entidad sobre la que se quiere informar. + *

+ * Construye el informe sobre la entidad indicada con las métricas por defecto + *

+ * + * @param entityId Identificador unívoco en el remoto de la entidad sobre la que + * se quiere informar. * @return El nuevo informe construido */ - + public ReportI buildReport(String entityId); + /** - *

Consulta una métrica específica para una entidad concreta

- * @param metricName métrica solicitada - * @param entityId Identificador unívoco en el remoto de la entidad sobre la que se consulta - * @return La nueva métrica construida tras la consulta al remoto - * @throws MetricException Si la métrica no está definida + *

+ * Consulta una métrica específica para una entidad concreta + *

+ * + * @param metricName métrica solicitada + * @param entityId Identificador unívoco en el remoto de la entidad sobre la + * que se consulta + * @return La nueva métrica construida tras la consulta al remoto + * @throws MetricException Si la métrica no esta definida */ - public Metric getMetric(String metricName,String entityId) throws MetricException; + public ReportItemI getMetric(String metricName, String entityId) throws MetricException; /** - *

Devuelve las métricas que el objeto RemoteEnquirer concreto puede obtener del servidor remoto

- * @return El listado de los nombres de métricas definidas + *

+ * Devuelve las métricas que el objeto RemoteEnquirer concreto puede obtener del + * servidor remoto + *

+ * + * @return El listado de los nombres de métricas definidas */ public List getAvailableMetrics(); + /** + *

+ * Devuelve el tipo de remoto al que se consulta + *

+ * @return El tipo del remoto consultado + */ + public RemoteType getRemoteType(); + } diff --git a/src/main/java/us/muit/fs/a4i/model/remote/package-info.java b/src/main/java/us/muit/fs/a4i/model/remote/package-info.java index 33a75679..fec4adf6 100644 --- a/src/main/java/us/muit/fs/a4i/model/remote/package-info.java +++ b/src/main/java/us/muit/fs/a4i/model/remote/package-info.java @@ -1,9 +1,17 @@ /** - *

Este paquete contiene las interfaces y clases especializadas en la consulta a remotos

- *

Construyen informes y métricas a partir de la información extraída de servidores remotos

- * Paquete para la consulta a remotos + *

+ * Este paquete contiene las interfaces y clases especializadas en la consulta a + * remotos + *

+ *

+ * Construyen informes y métricas a partir de la información extraída de + * servidores remotos + *

+ * Paquete para la consulta a remotos * - * @author Isabel Román - * @version 0.0 + * @author Isabel Román + * @version V.0.2 */ package us.muit.fs.a4i.model.remote; \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/persistence/ExcelReportManager.java b/src/main/java/us/muit/fs/a4i/persistence/ExcelReportManager.java index 61ea10e6..e250cdc6 100644 --- a/src/main/java/us/muit/fs/a4i/persistence/ExcelReportManager.java +++ b/src/main/java/us/muit/fs/a4i/persistence/ExcelReportManager.java @@ -1,226 +1,397 @@ -/** - * - */ package us.muit.fs.a4i.persistence; -import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.Collection; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import java.util.logging.Logger; import org.apache.poi.EncryptedDocumentException; -import org.apache.poi.hssf.usermodel.HSSFSheet; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.sl.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.CreationHelper; -import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.WorkbookFactory; - -import us.muit.fs.a4i.control.ReportManagerI; -import us.muit.fs.a4i.exceptions.ReportNotDefinedException; -import us.muit.fs.a4i.model.entities.Indicator; -import us.muit.fs.a4i.model.entities.Metric; -import us.muit.fs.a4i.model.entities.ReportI; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFFont; +import org.apache.poi.xssf.usermodel.XSSFCell; +import org.apache.poi.xssf.usermodel.XSSFCellStyle; +import org.apache.poi.xssf.usermodel.XSSFColor; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.model.StylesTable; +import us.muit.fs.a4i.exceptions.ReportNotDefinedException; +import us.muit.fs.a4i.model.entities.ReportI; +import us.muit.fs.a4i.model.entities.ReportItemI; +import us.muit.fs.a4i.model.entities.Font; /** - *

Clase que cotendrán las funciones de manejo de excel comunes al manejo de cualquier informe

- *

Se utiliza la API apachePOI para manejar los ficheros excel

- *

Las primeras versiones se centran en la escritura

- *

Política de informes: un informe es una hoja de un documento excel, identificada con el id del informe

- *

Este Gestor tiene los métodos para obtener la hoja y persistirla

- *

Si la hoja existía la recupera y se añadirá sobre ella, no se elimina lo anterior, si no existía se crea nueva

- * @author Isabel Román + *

+ * Clase que cotendrá las funciones de manejo de excel comunes al manejo de + * cualquier informe + *

+ *

+ * Se utiliza la API apachePOI para manejar los ficheros excel + *

+ *

+ * Las primeras versiones se centran en la escritura + *

+ *

+ * Política de informes: un informe es una hoja de un documento excel, + * identificada con el id del informe + *

+ *

+ * Este Gestor tiene los métodos para obtener la hoja y persistirla + *

+ *

+ * Si la hoja exist�a la recupera y se añadirá sobre ella, no se elimina lo + * anterior, si no existía se crea nueva + *

+ *

+ * Deuda técnica. En la persistencia de métricas e indicadores se observa mucho + * código replicado, se debe optimizar + *

+ * + * @author Isabel Román * * */ -public class ExcelReportManager implements PersistenceManager, FileManager{ - private static Logger log=Logger.getLogger(ExcelReportManager.class.getName()); +public class ExcelReportManager implements PersistenceManager, FileManager { + private static Logger log = Logger.getLogger(ExcelReportManager.class.getName()); + + private Map styles = new HashMap(); /** - *

Referencia al gestor de estilo que se va a utilizar

+ *

+ * Referencia al gestor de estilo que se va a utilizar + *

*/ protected ReportFormaterI formater; - ReportI report; - FileInputStream inputStream=null; - + + FileInputStream inputStream = null; + /** - *

Localización del fichero excel

+ *

+ * Localización del fichero excel + *

*/ - protected String filePath=""; + protected String filePath = ""; /** - *

Nombre del fichero excel

+ *

+ * Nombre del fichero excel + *

*/ - protected String fileName=""; - - protected HSSFWorkbook wb=null; - protected HSSFSheet sheet=null; - - public void setReport(ReportI report) { - log.info("Establece el informe"); - this.report=report; - } + protected String fileName = ""; + protected XSSFWorkbook wb = null; + protected XSSFSheet sheet = null; + public ExcelReportManager(String filePath, String fileName) { + super(); + this.filePath = filePath; + this.fileName = fileName; + } + + public ExcelReportManager() { + super(); + } @Override public void setFormater(ReportFormaterI formater) { log.info("Establece el formateador"); - this.formater=formater; - + this.formater = formater; + } @Override public void setPath(String path) { log.info("Establece la ruta al fichero"); - this.filePath=path; - + this.filePath = path; + } @Override public void setName(String name) { log.info("Establece el nombre del fichero"); - this.fileName=name; - + this.fileName = name; + } + /** - * El libro contendrán todos los informes de un tipo concreto - * Primero hay que abrir el libro - * Busco la hoja correspondiente a esta entidad, si ya existe la elimino - * Creo la hoja + *

+ * El libro contendrá todos los informes de un tipo concreto. Primero hay que + * abrir el libro. Busco la hoja correspondiente a esta entidad, si ya existe la + * elimino. Creo la hoja + *

+ * * @return Hoja de excel - * @throws IOException error al abrir el fichero + * @throws IOException error al abrir el fichero * @throws EncryptedDocumentException documento protegido */ - protected HSSFSheet getCleanSheet() throws EncryptedDocumentException, IOException { - log.info("Solicita una hoja nueva del libro manejado"); - if(wb==null) { - inputStream = new FileInputStream(filePath+fileName+".xls"); - wb = (HSSFWorkbook) WorkbookFactory.create(inputStream); + protected XSSFSheet getCleanSheet(String entityId) throws EncryptedDocumentException, IOException { + log.info("Solicita una hoja nueva del libro manejado, para la entidad con id: " + entityId); + if (wb == null) { + inputStream = new FileInputStream(filePath + fileName); + + wb = new XSSFWorkbook(inputStream); log.info("Generado workbook"); - + } - if(sheet==null) - { + if (sheet == null) { /** - int templateIndex=wb.getSheetIndex("Template"); - HSSFSheet sheet = wb.cloneSheet(templateIndex); - int newIndex=wb.getSheetIndex(sheet); - **/ + * int templateIndex=wb.getSheetIndex("Template"); HSSFSheet sheet = + * wb.cloneSheet(templateIndex); int newIndex=wb.getSheetIndex(sheet); + **/ /** - *

Verifico si la hoja existe y si es así la extraigo

- *

Si no existe la creo. + *

+ * Verifico si la hoja existe y si es así la extraigo + *

+ *

+ * Si no existe la creo. */ - sheet= wb.getSheet(report.getId().replaceAll("/", ".")); - - if(sheet!=null) { - log.info("Recuperada hoja, que ya existía"); - /* - * Si la hoja existe la elimino - */ - int index = wb.getSheetIndex(sheet); - wb.removeSheetAt(index); - } - sheet=wb.createSheet(report.getId().replaceAll("/", ".")); - log.info("Creada hoja nueva"); - + sheet = wb.getSheet(entityId.replaceAll("/", ".")); + + if (sheet != null) { + log.info("Recuperada hoja, que ya existía"); + /* + * Si la hoja existe la elimino + */ + int index = wb.getSheetIndex(sheet); + wb.removeSheetAt(index); + } + sheet = wb.createSheet(entityId.replaceAll("/", ".")); + log.info("Creada hoja nueva"); + } - - + return sheet; } + /** - * Guarda en un hoja limpia con el nombre del id del informe todas las métricas y los indicadores que incluya + * Un informe será una hoja en el libro excel Guarda en un hoja limpia con el + * nombre del id del informe Incluye todas las métricas y los indicadores que + * tenga report */ @Override - public void saveReport() throws ReportNotDefinedException{ - log.info("Guardando informe"); - if(report==null) { - throw new ReportNotDefinedException(); - } - try { - FileOutputStream out; - if(sheet==null) { - sheet=getCleanSheet(); - } - - /** - * A partir de la última que haya - * Fila 1: Encabezado métricas - * Filas 2 a N:Para cada métrica del informe una fila - * Fila N+1: Encabezado indicadores - * Filas N+2 a M: Para cada indicador una fila + public void saveReport(ReportI report) { + log.info("Guardando informe con id: " + report.getEntityId()); + try { + FileOutputStream out; + if (sheet == null) { + sheet = getCleanSheet(report.getEntityId()); + } + + /** + * A partir de la última que haya Fila 1: Encabezado métricas Filas 2 a N:Para + * cada métrica del informe una fila Fila N+1: Encabezado indicadores Filas N+2 + * a M: Para cada indicador una fila */ - int rowIndex=sheet.getLastRowNum(); - rowIndex++; - sheet.createRow(rowIndex).createCell(0).setCellValue("Métricas tomadas el día "); - sheet.getRow(rowIndex).createCell(1).setCellValue(Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)).toString()); - Collection collection=report.getAllMetrics(); - for(Metric metric:collection) { - persistMetric(metric); - } - - - out = new FileOutputStream(filePath+fileName+".xls"); - wb.write(out); - out.close(); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - private void persistMetric(Metric metric) { - log.info("Introduzco métrica en la hoja"); - - int rowIndex=sheet.getLastRowNum(); - rowIndex++; - Row row=sheet.createRow(rowIndex); - log.info("Indice de fila nueva "+rowIndex); - int cellIndex=0; - //Aquí debería incorporar el formato de fuente en las celdas - row.createCell(cellIndex++).setCellValue(metric.getName()); - row.createCell(cellIndex++).setCellValue(metric.getValue().toString()); - row.createCell(cellIndex++).setCellValue(metric.getUnit()); - row.createCell(cellIndex++).setCellValue(metric.getDescription()); - row.createCell(cellIndex++).setCellValue(metric.getSource()); - row.createCell(cellIndex).setCellValue(metric.getDate().toString()); - log.info("Indice de celda final"+cellIndex); - - } - - private void persistIndicator(Indicator indicator) { - log.info("Introduzco indicador en la hoja"); - - - int rowIndex=sheet.getLastRowNum(); - rowIndex++; - Row row=sheet.createRow(rowIndex); - log.info("Indice de fila nueva "+rowIndex); - int cellIndex=0; - //Aquí debería indicar el formato de fuente en las celdas, que dependerá del estado del índice - row.createCell(cellIndex++).setCellValue(indicator.getName()); - row.createCell(cellIndex++).setCellValue(indicator.getValue().toString()); - - row.createCell(cellIndex++).setCellValue(indicator.getDescription()); - - row.createCell(cellIndex).setCellValue(indicator.getDate().toString()); - log.info("Indice de celda final"+cellIndex); - - } + int rowIndex = sheet.getLastRowNum(); + rowIndex++; + sheet.createRow(rowIndex).createCell(0).setCellValue("Métricas guardadas el día "); + sheet.getRow(rowIndex).createCell(1) + .setCellValue(Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)).toString()); + Collection collection = report.getAllMetrics(); + for (ReportItemI metric : collection) { + persistMetric(metric); + rowIndex++; + } + // Ahora irían los indicadores + rowIndex++; + sheet.createRow(rowIndex).createCell(0).setCellValue("Indicadores"); + collection = report.getAllIndicators(); + for (ReportItemI indicator : collection) { + persistIndicator(indicator); + rowIndex++; + } + + out = new FileOutputStream(filePath + fileName); + wb.write(out); + out.close(); + } catch (Exception e) { + + e.printStackTrace(); + } + } + + private void persistMetric(ReportItemI metric) { + log.info("Introduzco métrica en la hoja"); + + int rowIndex = sheet.getLastRowNum(); + rowIndex++; + XSSFRow row = sheet.createRow(rowIndex); + log.info("Indice de fila nueva " + rowIndex); + int cellIndex = 0; + StylesTable stylesTable = wb.getStylesSource(); + stylesTable.ensureThemesTable(); + XSSFCellStyle style = styles.get("metricStyle"); + + if (style == null) { + + style = stylesTable.createCellStyle(); + XSSFFont poiFont = wb.createFont(); + + Font a4iFont = formater.getMetricFont(); + // Establezco el color y la fuente a utilizar en el texto de los indicadores. + byte[] color = { (byte) a4iFont.getColor().getRed(), (byte) a4iFont.getColor().getGreen(), + (byte) a4iFont.getColor().getBlue() }; + XSSFColor myColor = new XSSFColor(color); + log.info("El nuevo color es " + myColor.getARGBHex()); + + poiFont.setFontHeightInPoints((short) a4iFont.getFont().getSize()); + poiFont.setFontName(a4iFont.getFont().getFamily()); + poiFont.setColor(myColor); + poiFont.setBold(true); + poiFont.setItalic(false); + + log.info("La nueva fuente poi es " + poiFont.toString()); + + // style.setFillBackgroundColor(a4iFont.getColor().toString()); + style.setFont(poiFont); + style.setFillBackgroundColor(myColor); + styles.put("metricStyle", style); + log.info("Creado el estilo con indice " + style.getIndex()); + } + + XSSFCell cell; + + cell = row.createCell(cellIndex); + cell.setCellValue(metric.getName()); + cell.setCellStyle(style); + sheet.autoSizeColumn(cellIndex++); + + cell = row.createCell(cellIndex); + cell.setCellValue(metric.getValue().toString()); + cell.setCellStyle(style); + sheet.autoSizeColumn(cellIndex++); + + row.createCell(cellIndex).setCellValue(metric.getUnit()); + sheet.autoSizeColumn(cellIndex++); + + row.createCell(cellIndex).setCellValue(metric.getDescription()); + sheet.autoSizeColumn(cellIndex++); + + row.createCell(cellIndex).setCellValue(metric.getSource()); + sheet.autoSizeColumn(cellIndex++); + + row.createCell(cellIndex).setCellValue(metric.getDate().toString()); + sheet.autoSizeColumn(cellIndex); + log.info("Indice de celda final " + cellIndex); + + } + + private void persistIndicator(ReportItemI indicator) { + log.info("Introduzco indicador en la hoja"); + // Mantengo uno diferente porque en el futuro la información del indicador será + // distinta a la de la métrica + int rowIndex = sheet.getLastRowNum(); + rowIndex++; + XSSFRow row = sheet.createRow(rowIndex); + log.info("Indice de fila nueva " + rowIndex); + int cellIndex = 0; + StylesTable stylesTable = wb.getStylesSource(); + stylesTable.ensureThemesTable(); + + XSSFCellStyle style = styles.get(indicator.getIndicator().getState().toString()); + try { + if (style == null) { + + style = stylesTable.createCellStyle(); + XSSFFont poiFont = wb.createFont(); + + Font a4iFont = formater.getIndicatorFont(indicator.getIndicator().getState()); + // Establezco el color y la fuente a utilizar en el texto de los indicadores. + byte[] color = { (byte) a4iFont.getColor().getRed(), (byte) a4iFont.getColor().getGreen(), + (byte) a4iFont.getColor().getBlue() }; + XSSFColor myColor = new XSSFColor(color); + + log.info("El nuevo color es " + myColor.getARGBHex()); + + // myColor.setIndexed(newColor++); + + poiFont.setFontHeightInPoints((short) a4iFont.getFont().getSize()); + poiFont.setFontName(a4iFont.getFont().getFamily()); + poiFont.setColor(myColor); + poiFont.setBold(true); + poiFont.setItalic(false); + + log.info("La nueva fuente poi es " + poiFont.toString()); + + // style.setFillBackgroundColor(a4iFont.getColor().toString()); + style.setFont(poiFont); + style.setFillBackgroundColor(myColor); + + styles.put(indicator.getIndicator().getState().toString(), style); + + log.info("Creado el estilo con indice " + style.getIndex()); + } + XSSFCell cell; + + cell = row.createCell(cellIndex); + cell.setCellValue(indicator.getName()); + cell.setCellStyle(style); + sheet.autoSizeColumn(cellIndex++); + + cell = row.createCell(cellIndex); + cell.setCellValue(indicator.getValue().toString()); + cell.setCellStyle(style); + sheet.autoSizeColumn(cellIndex++); + + row.createCell(cellIndex).setCellValue(indicator.getUnit()); + sheet.autoSizeColumn(cellIndex++); + + row.createCell(cellIndex).setCellValue(indicator.getDescription()); + sheet.autoSizeColumn(cellIndex++); + + cell = row.createCell(cellIndex); + cell.setCellStyle(style); + log.info("Establecido el estilo con indice " + style.getIndex() + " en la celda " + cellIndex); + cell.setCellValue(indicator.getIndicator().getState().toString()); + sheet.autoSizeColumn(cellIndex++); + + row.createCell(cellIndex).setCellValue(indicator.getSource()); + sheet.autoSizeColumn(cellIndex++); + + row.createCell(cellIndex).setCellValue(indicator.getDate().toString()); + sheet.autoSizeColumn(cellIndex); + log.info("Indice de celda final " + cellIndex); + + } catch (IOException e) { + log.warning("Problema al abrir el fichero con los formatos"); + e.printStackTrace(); + } + + } @Override - public void deleteReport() throws ReportNotDefinedException { - // TODO Auto-generated method stub - - } - } + public void deleteReport(ReportI report) throws ReportNotDefinedException { + log.info("Eliminando informe excel"); + if (report == null) { + throw new ReportNotDefinedException(); + } + try { + inputStream = new FileInputStream(filePath + fileName); + wb = new XSSFWorkbook(inputStream); + log.info("Generado workbook"); + sheet = wb.getSheet(report.getEntityId()); + if (sheet != null) { + int index = wb.getSheetIndex(sheet); + wb.removeSheetAt(index); + FileOutputStream out; + out = new FileOutputStream(filePath + fileName); + wb.write(out); + out.close(); + + } else { + log.info("No existe el informe " + report.getEntityId()); + } + inputStream.close(); + } catch (Exception e) { + + e.printStackTrace(); + } + } +} diff --git a/src/main/java/us/muit/fs/a4i/persistence/FileManager.java b/src/main/java/us/muit/fs/a4i/persistence/FileManager.java index c23c824e..518d6877 100644 --- a/src/main/java/us/muit/fs/a4i/persistence/FileManager.java +++ b/src/main/java/us/muit/fs/a4i/persistence/FileManager.java @@ -4,20 +4,33 @@ package us.muit.fs.a4i.persistence; /** - *

Interfaz con los aspectos comunes al manejo de ficheros

- *

Se separa de ReportManager porque a veces los informes se pueden persistir en bases de datos, u otros sistemas de persistencia

- * @author Isabel Román + *

+ * Interfaz con los aspectos comunes al manejo de ficheros + *

+ *

+ * Se separa de ReportManager porque a veces los informes se pueden persistir en + * bases de datos, u otros sistemas de persistencia + *

+ * + * @author Isabel Román * */ public interface FileManager { /** - *

Establece la localización del fichero

- * @param path localización del fichero de informe + *

+ * Establece la localización del fichero + *

+ * + * @param path localización del fichero de informe */ public void setPath(String path); + /** - *

Establece el nombre que tendrá el fichero del informe

+ *

+ * Establece el nombre que tendrá el fichero del informe + *

+ * * @param name nombre del fichero del informe */ public void setName(String name); diff --git a/src/main/java/us/muit/fs/a4i/persistence/PersistenceManager.java b/src/main/java/us/muit/fs/a4i/persistence/PersistenceManager.java index 49b70c64..ead56d96 100644 --- a/src/main/java/us/muit/fs/a4i/persistence/PersistenceManager.java +++ b/src/main/java/us/muit/fs/a4i/persistence/PersistenceManager.java @@ -1,39 +1,50 @@ package us.muit.fs.a4i.persistence; -import us.muit.fs.a4i.control.ReportManagerI; import us.muit.fs.a4i.exceptions.ReportNotDefinedException; import us.muit.fs.a4i.model.entities.ReportI; -import us.muit.fs.a4i.model.remote.RemoteEnquirer; /** - *

Interfaz de los gestores de persistencia

- * @author isa + *

+ * Interfaz de los gestores de persistencia + *

+ * + * @author Isabel Román * */ + public interface PersistenceManager { + public static enum PersistenceType { + EXCEL, DDBB + } + /** - *

Establece el informe que se va a manejar

- * @param report El informe a manejar - */ - void setReport(ReportI report); - - /** - *

Establece el elemento que establece el formato

- * @param formater Elemento que maneja las características de formato + *

+ * Establece el elemento que establece el formato + *

+ * + * @param formater Elemento que maneja las características de formato */ void setFormater(ReportFormaterI formater); - + /** - *

Persiste el informe

- * @throws ReportNotDefinedException Si no se estableció el informe lanzará error + *

+ * Persiste el informe + *

+ * + * @param report informe a persistir + * @throws ReportNotDefinedException si el informe es nulo lanzará excepción */ - void saveReport() throws ReportNotDefinedException; + void saveReport(ReportI report) throws ReportNotDefinedException; + /** - *

Borra el informe

+ *

+ * Borra el informe + *

* - * @throws ReportNotDefinedException Si no se estableció el informe dará error + * @param report informe a borrar + * @throws ReportNotDefinedException si el informe es nulo dará error */ - void deleteReport()throws ReportNotDefinedException; - + void deleteReport(ReportI report) throws ReportNotDefinedException; + } \ No newline at end of file diff --git a/src/main/java/us/muit/fs/a4i/persistence/ReportFormater.java b/src/main/java/us/muit/fs/a4i/persistence/ReportFormater.java index d2b03db0..7225b4a4 100644 --- a/src/main/java/us/muit/fs/a4i/persistence/ReportFormater.java +++ b/src/main/java/us/muit/fs/a4i/persistence/ReportFormater.java @@ -3,16 +3,15 @@ */ package us.muit.fs.a4i.persistence; -import java.awt.Font; import java.io.IOException; import java.util.HashMap; import us.muit.fs.a4i.config.Context; -import us.muit.fs.a4i.model.entities.Indicator; -import us.muit.fs.a4i.model.entities.Indicator.State; +import us.muit.fs.a4i.model.entities.Font; +import us.muit.fs.a4i.model.entities.IndicatorI; /** - * @author Isabel Román + * @author Isabel Román * */ public class ReportFormater implements ReportFormaterI { @@ -21,54 +20,65 @@ public class ReportFormater implements ReportFormaterI { */ private Font defaultFont; /** - * Formato de fuente para las métricas + * Formato de fuente para las métricas */ private Font metricFont; /** - * Fomatos de fuente en función del estado del indicador + * Fomatos de fuente en función del estado del indicador */ - private HashMap indicatorsFont; - - ReportFormater(){ - this.indicatorsFont=new HashMap(); - //Sólo se construye el mapa, conforme se vayan solicitando se irán rellenando - } + private HashMap indicatorsFont; + + public ReportFormater() { + this.indicatorsFont = new HashMap(); + // Sólo se construye el mapa, conforme se vayan solicitando se irán rellenando + } + @Override public Font getMetricFont() { - if (metricFont==null) { - metricFont=Context.getMetricFont(); + if (metricFont == null) { + try { + metricFont = Context.getContext().getMetricFont(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } return metricFont; } @Override public void setMetricFont(Font font) { - metricFont=font; + metricFont = font; } @Override - public Font getIndicatorFont(Indicator.State state) throws IOException { - if (!indicatorsFont.containsKey(state)){ + public Font getIndicatorFont(IndicatorI.IndicatorState state) throws IOException { + if (!indicatorsFont.containsKey(state)) { try { - indicatorsFont.put(state, Context.getIndicatorFont(state)); + indicatorsFont.put(state, Context.getContext().getIndicatorFont(state)); } catch (IOException e) { indicatorsFont.put(state, Context.getContext().getDefaultFont()); } - } - + } + return indicatorsFont.get(state); } @Override - public void setIndicatorFont(State state, Font font) { + public void setIndicatorFont(IndicatorI.IndicatorState state, Font font) { indicatorsFont.put(state, font); } @Override public void setDefaultFont(Font font) { - defaultFont=font; + defaultFont = font; + } + + @Override + public Font getDefaultFont() { + return this.defaultFont; } - + } diff --git a/src/main/java/us/muit/fs/a4i/persistence/ReportFormaterI.java b/src/main/java/us/muit/fs/a4i/persistence/ReportFormaterI.java index dba94f19..e6b8c26c 100644 --- a/src/main/java/us/muit/fs/a4i/persistence/ReportFormaterI.java +++ b/src/main/java/us/muit/fs/a4i/persistence/ReportFormaterI.java @@ -3,46 +3,83 @@ */ package us.muit.fs.a4i.persistence; -import java.awt.Font; import java.io.IOException; -import us.muit.fs.a4i.model.entities.Indicator; +import us.muit.fs.a4i.model.entities.Font; +import us.muit.fs.a4i.model.entities.IndicatorI; /** - *

Interfaz genérica para establecer el estilo de los informes si se persisten en un medio "visual"

- * @author Isabel Román + *

+ * Interfaz genérica para establecer el estilo de los informes si se persisten + * en un medio "visual" + *

+ * + * @author Isabel Rom�n * */ public interface ReportFormaterI { /** - *

Devuelve el formato que debe tener la fuente de una métrica

- * @return Fuente de la métrica + *

+ * Devuelve el formato que debe tener la fuente de una métrica + *

+ * + * @return Fuente de la métrica */ Font getMetricFont(); + /** - *

Establece la fuente para las métricas

- * @param font Fuente de la métrica + *

+ * Establece la fuente para las métricas + *

+ * + * @param font Fuente de la métrica */ void setMetricFont(Font font); + /** - *

Recupera las fuente para un indicador con el estado indicado.

- *

Si no se ha configurado para ese estado una fuente específica se devuelve la fuente por defecto

+ *

+ * Recupera las fuente para un indicador con el estado indicado. + *

+ *

+ * Si no se ha configurado para ese estado una fuente específica se devuelve la + * fuente por defecto + *

+ * * @param state Estado para el que solicita la fuente * @return La fuente a utilizar para los indicadores con el estado indicado - * @throws IOException Si no se puede leer la configuración + * @throws IOException Si no se puede leer la configuración */ - - Font getIndicatorFont(Indicator.State state) throws IOException; + + Font getIndicatorFont(IndicatorI.IndicatorState state) throws IOException; + /** - *

Establece la fuente para un indicador en un estado determinado

- * @param state El estado correspondiente a la fuente que se está estableciendo - * @param font La fuente a usar cuando el estado del indicador es el señalado en la parámetro state + *

+ * Establece la fuente para un indicador en un estado determinado + *

+ * + * @param state El estado correspondiente a la fuente que se está estableciendo + * @param font La fuente a usar cuando el estado del indicador es el se�alado + * en la par�metro state */ - void setIndicatorFont(Indicator.State state, Font font); + void setIndicatorFont(IndicatorI.IndicatorState state, Font font); + /** - *

Establece la fuente por defecto para los indicadores y métricas

- *

Usada cuando el estado no está diferenciado o no se ha especificado una fuente para las métricas

+ *

+ * Establece la fuente por defecto para los indicadores y m�tricas + *

+ *

+ * Usada cuando el estado no est� diferenciado o no se ha especificado una + * fuente para las m�tricas + *

+ * * @param font Fuente por defecto */ void setDefaultFont(Font font); + + /** + * Devuelve la fuente por defecto + * + * @return Fuente por defecto + */ + Font getDefaultFont(); } diff --git a/src/main/java/us/muit/fs/a4i/persistence/package-info.java b/src/main/java/us/muit/fs/a4i/persistence/package-info.java index 10c8620b..bd66caed 100644 --- a/src/main/java/us/muit/fs/a4i/persistence/package-info.java +++ b/src/main/java/us/muit/fs/a4i/persistence/package-info.java @@ -1,7 +1,13 @@ /** - * Este paquete contiene las clases que facilitan el acceso a los datos persistentes + *

+ * Este paquete contiene las clases que facilitan el acceso a los datos + * persistentes + *

+ * Paquete de persistencia * - * @author Isabel Román - * @version 0.0 + * @author Isabel Román + * @version V.0.2 */ package us.muit.fs.a4i.persistence; \ No newline at end of file diff --git a/src/main/resources/a4i.conf b/src/main/resources/a4i.conf index 7a5c087e..71ebc8a7 100644 --- a/src/main/resources/a4i.conf +++ b/src/main/resources/a4i.conf @@ -1,9 +1,28 @@ -#Fichero de configuración por defecto de la api +#Fichero de configuracion por defecto de la api #Tipo de persistencia que se quiere usar -persistence.type=excel +persistence.type=EXCEL #Tipo de remoto que se quiere usar -remote.type=github +remote.type=GITHUB + +#Caracteristicas de fuente de las métricas +Font.metric.color=green +Font.metric.height=10 +Font.metric.type=Serif + +#Caracteristicas para la fuente de indicadores según estados del mismo +Font.OK.color=Black +Font.OK.height=10 +Font.OK.type=Serif + +Font.WARNING.color=Orange +Font.WARNING.height=10 +Font.WARNING.type=Serif + +Font.CRITICAL.color=Red +Font.CRITICAL.height=12 +Font.CRITICAL.type=Arial + #Caracteristicas por defecto de la fuente, cuando los informes son visuales -Font.default.color=Black -Font.default.height=12 -Font.default.type=Times \ No newline at end of file +Font.default.color=black +Font.default.height=10 +Font.default.type=Arial \ No newline at end of file diff --git a/src/main/resources/a4iDefault.json b/src/main/resources/a4iDefault.json index 58f5d6d2..6b6965fe 100644 --- a/src/main/resources/a4iDefault.json +++ b/src/main/resources/a4iDefault.json @@ -1,10 +1,35 @@ { - "metrics": [{ + "metrics": [ + { "name": "subscribers", "type": "java.lang.Integer", - "description": "Número de suscriptores, watchers en la web", + "description": "Número de suscriptores de un repositorio, watchers en la web", "unit": "subscribers" }, + { + "name": "collaborators", + "type": "java.lang.Integer", + "description": "Número de colaboradores de un repositorio", + "unit": "collaborators" + }, + { + "name": "ownerCommits", + "type": "java.lang.Integer", + "description": "Número de commits del propietario del repositorio", + "unit": "commits" + }, + { + "name": "members", + "type": "java.lang.Integer", + "description": "Miembros de una organización", + "unit": "members" + }, + { + "name": "teams", + "type": "java.lang.Integer", + "description": "Equipos de una organización", + "unit": "teams" + }, { "name": "forks", "type": "java.lang.Integer", @@ -14,14 +39,26 @@ { "name": "watchers", "type": "java.lang.Integer", - "description": "Observadores, en la web aparece com forks", + "description": "Observadores de un repositorio, en la web aparece com forks", "unit": "watchers" }, { - "name": "issues", + "name": "followers", "type": "java.lang.Integer", - "description": "Tareas sin finalizar en el repositorio", - "unit": "issues" + "description": "Seguidores de una organización", + "unit": "followers" + }, + { + "name": "totalAdditions", + "type": "java.lang.Integer", + "description": "Inserciones desde que se inició el repositorio", + "unit": "additions" + }, + { + "name": "totalDeletions", + "type": "java.lang.Integer", + "description": "Eliminaciones desde que se inició el repositorio", + "unit": "deletions" }, { "name": "stars", @@ -38,21 +75,276 @@ { "name": "lastPush", "type": "java.util.Date", - "description": "Último push realizado en el repositorio", + "description": "Último push realizado en el repositorio", "unit": "date" - } + }, + { + "name": "lastUpdated", + "type": "java.util.Date", + "description": "Última actualización realizada en el repositorio", + "unit": "date" + }, + { + "name": "openIssues", + "type": "java.lang.Integer", + "description": "Numero de issues abiertas", + "unit": "issues" + }, + { + "name": "openProjects", + "type": "java.lang.Integer", + "description": "Proyectos abiertos", + "unit": "projects" + }, + { + "name": "closedProjects", + "type": "java.lang.Integer", + "description": "Proyectos cerrados", + "unit": "projects" + }, + { + "name": "repositories", + "type": "java.lang.Integer", + "description": "Número de repositorios", + "unit": "repositories" + }, + { + "name": "repositoriesWithPullRequest", + "type": "java.lang.Integer", + "description": "Número de repositorios con pull requests", + "unit": "repositories" + }, + { + "name": "repositoriesWithOpenPullRequest", + "type": "java.lang.Integer", + "description": "Número de repositorios con pull requests pendientes", + "unit": "repositories" + }, + { + "name": "closedIssues", + "type": "java.lang.Integer", + "description": "Numero de issues cerrados", + "unit": "issues" + }, + { + "name": "issues", + "type": "java.lang.Integer", + "description": "Tareas totales", + "unit": "issues" + }, + { + "name": "issuesLastMonth", + "type": "java.lang.Integer", + "description": "Número de trabajos/issues creados en el mes", + "unit": "issues/month" + }, + { + "name": "closedIssuesLastMonth", + "type": "java.lang.Integer", + "description": "Número de repositorios trabajos/issues concluidos en el mes", + "unit": "issues/month" + }, + { + "name": "meanClosedIssuesLastMonth", + "type": "java.lang.Double", + "description": "Media de issues cerrados en el pasado mes por desarrollador", + "unit": "issues/developer" + }, + { + "name": "assignedIssuesLastMonth", + "type": "java.lang.Integer", + "description": "Issues asignados a un desarrollador en el último mes", + "unit": "issues/month" + }, + { + "name": "pullRequests", + "type": "java.lang.Integer", + "description": "Número de pull requests", + "unit": "pull requests" + }, + { + "name": "totalPullReq", + "type": "java.lang.Integer", + "description": "Número de pull requests en un repositorio", + "unit": "PR" + }, + { + "name": "closedPullReq", + "type": "java.lang.Integer", + "description": "Número de pull requests cerradas en un repositorio", + "unit": "PR" + }, + { + "name": "PRAcceptedLastYear", + "type": "java.lang.Integer", + "description": "Número de pull requests aceptadas el pasado año", + "unit": "PR" + }, + { + "name": "PRAcceptedLastMonth", + "type": "java.lang.Integer", + "description": "Número de pull requests aceptadas el pasado mes", + "unit": "PR" + }, + { + "name": "PRRejectedLastMonth", + "type": "java.lang.Integer", + "description": "Número de pull requests rechazadas el pasado mes", + "unit": "PR" + }, + { + "name": "PRRejectedLastYear", + "type": "java.lang.Integer", + "description": "Número de pull requests rechazadas el pasado año", + "unit": "PR" + }, + { + "name": "conventionalCommits", + "type": "java.lang.Double", + "description": "Ratio de commits convencionales", + "unit": "ratio" + }, + { + "name": "commitsWithDescription", + "type": "java.lang.Double", + "description": "Ratio de commits con descripción", + "unit": "ratio" + }, + { + "name": "issuesWithLabels", + "type": "java.lang.Double", + "description": "Ratio de issues con más de una etiqueta", + "unit": "ratio" + }, + { + "name": "gitFlowBranches", + "type": "java.lang.Double", + "description": "Ratio de ramas que siguen gitflow", + "unit": "ratio" + }, + { + "name": "conventionalPullRequests", + "type": "java.lang.Double", + "description": "Ratio de PRs convencionales", + "unit": "ratio" + }, + { + "name": "teamsBalance", + "type": "java.lang.Integer", + "description": "Balance de equipos y open issues", + "unit": "ratio" + }, + { + "name": "cycleTime", + "type": "java.lang.Double", + "description": "Tiempo medio que tarda una tarea en completarse", + "unit": "horas" + }, + { + "name": "waitTime", + "type": "java.lang.Double", + "description": "Tiempo medio que las tareas permanecen en espera o bloqueadas", + "unit": "horas" + }, + { + "name": "throughput", + "type": "java.lang.Double", + "description": "Número de tareas completadas en un período determinado", + "unit": "tareas/semana" + }, + { + "name": "WIP", + "type": "java.lang.Double", + "description": "Promedio de tareas en curso (trabajo en progreso)", + "unit": "tareas" + } ], - "indicators": [{ - "name": "issuesProgess", + "indicators": [ + { + "name": "issuesProgress", "type": "java.lang.Double", "description": "Ratio de issues cerrados frente a totales", - "unit": "ratio" + "unit": "ratio", + "limits": { + "ok": 2, + "warning": 4, + "critical": 6 + } }, { "name": "overdued", "type": "java.lang.Double", "description": "Ratio de issues vencidos frente a abiertos", + "unit": "ratio", + "limits": { + "ok": 5, + "warning": 9, + "critical": 12 + } + }, + { + "name": "issuesRatio", + "type": "java.lang.Double", + "description": "Ratio de issues abiertos frente a cerrados", "unit": "ratio" + }, + { + "name": "pullRequestCompletion", + "type": "java.lang.Double", + "description": "% PR cerrados frente a totales", + "unit": "%", + "limits": { + "ok": 75, + "warning": 50, + "critical": 25 + } + }, + { + "name": "developerPerfomance", + "type": "java.lang.Double", + "description": "Ratio de issues cerradas frente a creadas del desarrollador frente al equipo", + "unit": "ratio" + }, + { + "name": "PRPerformance", + "type": "java.lang.Double", + "description": "% de PR rechazados en el mes comparado con el % rechazado en el año", + "unit": "%", + "limits": { + "ok": 25, + "warning": 50, + "critical": 75 + } + }, + { + "name": "conventionsCompliant", + "type": "java.lang.Double", + "description": "Indicador de conformidad con las convenciones en el repo", + "unit": "ratio" + }, + { + "name": "teamsBalanceI", + "type": "java.lang.Double", + "description": "Balance de equipos y open issues", + "unit": "ratio", + "limits": { "ok": 0.2, "warning": 0.6, "critical": 0.8 } + }, + { + "name": "fixTime", + "type": "java.lang.Double", + "description": "Tiempo para arreglos", + "unit": "ratio" + }, + { + "name": "IEF", + "type": "java.lang.Double", + "description": "Ãndice de Eficiencia del Flujo", + "unit": "índice", + "limits": { + "ok": 0.8, + "warning": 0.6, + "critical": 0.4 + } } ] } \ No newline at end of file diff --git a/src/main/resources/log.properties b/src/main/resources/log.properties index 75939489..8a387663 100644 --- a/src/main/resources/log.properties +++ b/src/main/resources/log.properties @@ -1,7 +1,18 @@ +# para que este fichero de configuración de trazas sea efectivo es necesario pasarlo como parámetro de la máquina virtual al ejecutar +#-Djava.util.logging.config.file="src\main\resources\log.properties" + # especificacion de detalle de log # nivel de log global .level = ALL javax.json.Json.level = FINEST +us.muit.fs.a4i.model.remote.GitHubRepositoryEnquirer.level = FINEST + +us.muit.fs.a4i.persistence.ExcelReportManager.level = FINEST +us.muit.fs.a4i.config.Context.level=FINEST +us.muit.fs.a4i.config.Checker.level=FINEST +us.muit.fs.a4i.config.IndicatorConfiguration.level=FINEST +us.muit.fs.a4i.config.MetricConfiguration.level=FINEST +us.muit.fs.a4i.control.calculators.RepositoryCalculator.level = FINEST # nivel clases @@ -14,7 +25,7 @@ handlers = java.util.logging.FileHandler,java.util.logging.ConsoleHandler # configuración de manejador de archivos # nivel soportado para archivos -java.util.logging.FileHandler.level = INFO +java.util.logging.FileHandler.level = ALL # archivo de almacenamiento de las salidas de log java.util.logging.FileHandler.pattern = Log/trazas$.log # maximo tamaño de archivo en bytes @@ -30,7 +41,7 @@ java.util.logging.FileHandler.append = false # configuración de manejador de consola # nivel soportado para consola -java.util.logging.ConsoleHandler.level = INFO +java.util.logging.ConsoleHandler.level = ALL # clase para formatear salida hacia consola #java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter java.util.logging.ConsoleHandler.formatter = java.util.logging.AnsiFormatter \ No newline at end of file diff --git a/src/test/java/us/muit/fs/a4i/test/config/CheckerTest.java b/src/test/java/us/muit/fs/a4i/test/config/CheckerTest.java deleted file mode 100644 index e1647899..00000000 --- a/src/test/java/us/muit/fs/a4i/test/config/CheckerTest.java +++ /dev/null @@ -1,238 +0,0 @@ -/** - * - */ -package us.muit.fs.a4i.test.config; - -import static org.junit.jupiter.api.Assertions.*; - -import java.io.File; -import java.io.FileNotFoundException; -import java.util.HashMap; -import java.util.logging.Logger; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import us.muit.fs.a4i.config.Checker; - -/** - * Test de la clase Checker que verifica las métricas e indicadores - * - * @author Isabel Román - * @see org.junit.jupiter.api.Tag - * - */ - -@Tag("unidad") -class CheckerTest { - private static Logger log = Logger.getLogger(CheckerTest.class.getName()); - static Checker underTest; - static String appConfPath; - - /** - * @throws java.lang.Exception - * @see org.junit.jupiter.api.BeforeAll - */ - @BeforeAll - static void setUpBeforeClass() throws Exception { - //Acciones a realizar antes de ejecutar los tests de esta clase - appConfPath="src"+File.separator+"test"+File.separator+"resources"+File.separator+"appConfTest.json"; - } - - /** - * @throws java.lang.Exception - * @see org.junit.jupiter.api.AfterAll - */ - @AfterAll - static void tearDownAfterClass() throws Exception { - //Acciones a realizar después de ejecutar todos los tests de esta clase - } - - /** - * @throws java.lang.Exception - * @see org.junit.jupiter.api.BeforeEach - */ - @BeforeEach - void setUp() throws Exception { - //Acciones a realizar antes de cada uno de los tests de esta clase - //Creo el objeto bajo test, un Checker - underTest = new Checker(); - } - - /** - * @throws java.lang.Exception - * @see org.junit.jupiter.api.AfterEach - */ - @AfterEach - void tearDown() throws Exception { - //Acciones a realizar después de cada uno de los tests de esta clase - } - - /** - * Test para el método que establece el fichero de configuración de la aplicación - * {@link us.muit.fs.a4i.config.Checker#setAppMetrics(java.lang.String)}. - */ - @Test - void testSetAppMetrics() { - fail("Not yet implemented"); // TODO - } - - /** - * Test para verificar el método - * {@link us.muit.fs.a4i.config.Checker#definedMetric(java.lang.String, java.lang.String)}. - * Si la métrica está definida y el tipo de valor que se quiere establecer es el adecuado - * debe devolver un hashmap con los datos de la métrica, usando como clave las etiquetas: - *
    - *
  • description
  • - *
  • unit
  • - *
- * Las métricas pueden estar definidas en el fichero de configuración de la api (a4iDefault.json) o en otro fichero - * configurado por la aplicación cliente. Para los test este fichero es appConfTest.json y se guarda - * junto al código de test, en la carpeta resources - * @see org.junit.jupiter.api.Tag - * @see org.junit.jupiter.api.Test - * @see org.junit.jupiter.api.DisplayName - * - * Test para verificar el método - * {@link us.muit.fs.a4i.config.Checker#definedMetric(java.lang.String, java.lang.String)}. - */ - @Test - @Tag("unidad") - @DisplayName("Prueba para el método definedMetric, que verifica si la métrica está definida con un tipo determinado y devuelve su configuración") - void testDefinedMetric() { - - //Creo valores Mock para verificar si comprueba bien el tipo - //Las métricas del test son de enteros, así que creo un entero y un string (el primero no dará problemas el segundo sí) - Integer valOKMock = Integer.valueOf(3); - String valKOMock = "KO"; - HashMap returnedMap=null; - //Primero, sin fichero de configuración de aplicación - try { - //Consulta una métrica no definida, con valor de tipo entero - //debe devolver null, no está definida - log.info("Busco la métrica llamada downloads"); - returnedMap=underTest.definedMetric("downloads", valOKMock.getClass().getName()); - assertNull(returnedMap, "Debería ser nulo, la métrica noexiste no está definida"); - - //Busco la métrica watchers con valor entero, no debería dar problemas - log.info("Busco la métrica watchers"); - returnedMap=underTest.definedMetric("watchers", valOKMock.getClass().getName()); - assertNotNull(returnedMap,"Debería devolver un hashmap, la métrica está definida"); - assertTrue(returnedMap.containsKey("unit"),"La clave unit tiene que estar en el mapa"); - assertTrue(returnedMap.containsKey("description"),"La clave description tiene que estar en el mapa"); - - //Busco una métrica que existe pero con un tipo incorrecto en el valor - assertNull(underTest.definedMetric("watchers", valKOMock.getClass().getName()), - "Debería ser nulo, la métrica está definida para Integer"); - } catch (FileNotFoundException e) { - fail("El fichero está en la carpeta resources"); - e.printStackTrace(); - } - - //Ahora establezco el fichero de configuración de la aplicación, con un nombre de fichero que no existe - underTest.setAppMetrics("pepe"); - try { - //Busco una métrica que se que no está en la configuración de la api - returnedMap=underTest.definedMetric("downloads", valOKMock.getClass().getName()); - fail("Debería lanzar una excepción porque intenta buscar en un fichero que no existe"); - } catch (FileNotFoundException e) { - log.info("Lanza la excepción adecuada, FileNotFoud"); - } catch (Exception e) { - fail("Lanza la excepción equivocada " + e); - } - - //Ahora establezco un fichero de configuración de la aplicación que sí existe - underTest.setAppMetrics(appConfPath); - try { - //Busco una métrica que se que no está en la configuración de la api pero sí en la de la aplicación - log.info("Busco la métrica llamada downloads"); - returnedMap=underTest.definedMetric("downloads", valOKMock.getClass().getName()); - assertNotNull(returnedMap,"Debería devolver un hashmap, la métrica está definida"); - assertTrue(returnedMap.containsKey("unit"),"La clave unit tiene que estar en el mapa"); - assertTrue(returnedMap.containsKey("description"),"La clave description tiene que estar en el mapa"); - } catch (FileNotFoundException e) { - fail("No debería devolver esta excepción"); - } catch (Exception e) { - fail("Lanza una excepción no reconocida " + e); - } - - } - - - - /** - * @see org.junit.jupiter.api.Tag - * @see org.junit.jupiter.api.Test - * @see org.junit.jupiter.api.DisplayName - * - * Test para el método - * {@link us.muit.fs.a4i.config.Checker#definedIndicator(java.lang.String, java.lang.String)}. - */ - @Test - @Tag("unidad") - @DisplayName("Prueba para el método definedIndicator, que verifica si el indicador está definido con un tipo determinado y devuelve su configuración") - void testDefinedIndicator() { - - //Creo valores Mock para verificar si comprueba bien el tipo - //Las métricas del test son de enteros, así que creo un entero y un string (el primero no dará problemas el segundo sí) - Double valOKMock = Double.valueOf(0.3); - String valKOMock = "KO"; - HashMap returnedMap=null; - //Primero, sin fichero de configuración de aplicación - try { - //Consulta un indicador no definido, con valor de tipo entero - //debe devolver null, no está definido - log.info("Busco el indicador llamado pullReqGlory"); - returnedMap=underTest.definedIndicator("pullReqGlory", valOKMock.getClass().getName()); - assertNull(returnedMap, "Debería ser nulo, el indicador pullReqGlory no está definido"); - - //Busco el indicador overdued con valor double, no debería dar problemas - log.info("Busco el indicador overdued"); - returnedMap=underTest.definedIndicator("overdued", valOKMock.getClass().getName()); - assertNotNull(returnedMap,"Debería devolver un hashmap, el indicador overdued está definido"); - assertTrue(returnedMap.containsKey("unit"),"La clave unit tiene que estar en el mapa"); - assertTrue(returnedMap.containsKey("description"),"La clave description tiene que estar en el mapa"); - - //Busco una métrica que existe pero con un tipo incorrecto en el valor - assertNull(underTest.definedIndicator("overdued", valKOMock.getClass().getName()), - "Debería ser nulo, el indicador overdued está definido para Double"); - } catch (FileNotFoundException e) { - fail("El fichero está en la carpeta resources"); - e.printStackTrace(); - } - - //Ahora establezco el fichero de configuración de la aplicación, con un nombre de fichero que no existe - underTest.setAppMetrics("pepe"); - try { - //Busco un indicador que se que no está en la configuración de la api - returnedMap=underTest.definedIndicator("pullReqGlory", valOKMock.getClass().getName()); - fail("Debería lanzar una excepción porque intenta buscar en un fichero que no existe"); - } catch (FileNotFoundException e) { - log.info("Lanza la excepción adecuada, FileNotFoud"); - } catch (Exception e) { - fail("Lanza la excepción equivocada " + e); - } - - //Ahora establezco un fichero de configuración de la aplicación que sí existe - underTest.setAppMetrics(appConfPath); - try { - //Busco una métrica que se que no está en la configuración de la api pero sí en la de la aplicación - log.info("Busco el indicador llamado pullReqGlory"); - returnedMap=underTest.definedIndicator("pullReqGlory", valOKMock.getClass().getName()); - assertNotNull(returnedMap,"Debería devolver un hashmap, el indicador está definido"); - assertTrue(returnedMap.containsKey("unit"),"La clave unit tiene que estar en el mapa"); - assertTrue(returnedMap.containsKey("description"),"La clave description tiene que estar en el mapa"); - } catch (FileNotFoundException e) { - fail("No debería devolver esta excepción"); - } catch (Exception e) { - fail("Lanza una excepción no reconocida " + e); - } - - } - -} diff --git a/src/test/java/us/muit/fs/a4i/test/config/ContextTest.java b/src/test/java/us/muit/fs/a4i/test/config/ContextTest.java index 0b3df017..a1d1cb8f 100644 --- a/src/test/java/us/muit/fs/a4i/test/config/ContextTest.java +++ b/src/test/java/us/muit/fs/a4i/test/config/ContextTest.java @@ -1,10 +1,17 @@ /** - * + * Tests para ContextTest sin modificar los ficheros de ocnfiguraicón pro defecto en ningún momento */ package us.muit.fs.a4i.test.config; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.awt.Color; +import java.io.File; import java.io.IOException; import java.util.logging.Logger; @@ -12,22 +19,40 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import us.muit.fs.a4i.config.Context; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; +import us.muit.fs.a4i.model.entities.Font; // Clase que sustituye a java.awt.Font + /** - * @author Isabel Román - * + * @author Isabel Román Verificación de la clase Context cuando sólo se utiliza + * el fichero de configuración por defecto (a4i.conf) */ class ContextTest { - private static Logger log = Logger.getLogger(CheckerTest.class.getName()); + private static Logger log = Logger.getLogger(ContextTest.class.getName()); + /** + * Ruta al fichero de configuración de indicadores y métricas establecidos por + * la aplicación + */ + static String appConfPath; + /** + * Ruta al fichero de configuración de propiedades de funcionamiento + * establecidos por la aplicación + */ + static String appPath; + /** * @throws java.lang.Exception + * + * @BeforeAll static void setUpBeforeClass() throws Exception { appConfPath = + * "src" + File.separator + "test" + File.separator + "resources" + + * File.separator + "appConfTest.json"; appPath = "src" + + * File.separator + "test" + File.separator + "resources" + + * File.separator + "appTest.conf"; } */ - @BeforeAll - static void setUpBeforeClass() throws Exception { - } /** * @throws java.lang.Exception @@ -48,6 +73,7 @@ void setUp() throws Exception { */ @AfterEach void tearDown() throws Exception { + // Ejecutar tras cada test } /** @@ -55,15 +81,16 @@ void tearDown() throws Exception { */ @Test void testGetContext() { - fail("Not yet implemented"); - } + try { + assertNotNull(Context.getContext(), "Devuelve null"); + assertTrue(Context.getContext() instanceof us.muit.fs.a4i.config.Context, "No es del tipo apropiado"); + assertSame(Context.getContext(), Context.getContext(), "No se devuelve el mismo contexto siempre"); + + } catch (IOException e) { + fail("No debería lanzar esta excepción"); + e.printStackTrace(); + } - /** - * Test method for {@link us.muit.fs.a4i.config.Context#setAppConf(java.lang.String)}. - */ - @Test - void testSetAppConf() { - fail("Not yet implemented"); } /** @@ -71,7 +98,15 @@ void testSetAppConf() { */ @Test void testGetChecker() { - fail("Not yet implemented"); + try { + assertNotNull(Context.getContext().getChecker(), "No devuelve el checker"); + assertTrue(Context.getContext().getChecker() instanceof us.muit.fs.a4i.config.Checker, + "No es del tipo apropiado"); + + } catch (IOException e) { + fail("No debería devolver esta excepción"); + e.printStackTrace(); + } } /** @@ -79,12 +114,13 @@ void testGetChecker() { */ @Test void testGetPersistenceType() { - try { - assertEquals("excel",Context.getContext().getPersistenceType(),"Debería estar definido el tipo excel por defecto en a4i.conf"); + try { + assertEquals("EXCEL", Context.getContext().getPersistenceType(), + "En el fichero de configuración por defecto, a4i.conf, está definido el tipo excel"); } catch (IOException e) { fail("El fichero no se localiza"); e.printStackTrace(); - } + } } /** @@ -93,43 +129,138 @@ void testGetPersistenceType() { @Test void testGetRemoteType() { try { - assertEquals("github",Context.getContext().getRemoteType(),"Debería estar definido el tipo github por defecto en a4i.conf"); + assertEquals("GITHUB", Context.getContext().getRemoteType(), + "En el fichero de configuración por defecto, a4i.conf, está el tipo github"); } catch (IOException e) { fail("El fichero no se localiza"); e.printStackTrace(); - } + } } /** + *

+ * Este test permite verificar que se lee bien la fuente, además es + * independiente del orden de ejecución del resto de test. La complejidad de la + * verifiación está impuesta por estar probando un singleton + *

* Test method for {@link us.muit.fs.a4i.config.Context#getDefaultFont()}. */ @Test void testGetDefaultFont() { - fail("Not yet implemented"); + try { + Font font = null; + String color; // No entiendo cómo usarlo, ¿para darle valor luego?// + // Uso esto para ver los tipos de fuentes de los que dispongo + // String[] fontNames = + // GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + // log.info("listado de fuentes " + Arrays.toString(fontNames)); + font = Context.getContext().getDefaultFont(); + assertNotNull(font, "No se ha inicializado bien la fuente"); + // Al ser Context un singleton una vez creada la instancia no se puede eliminar + // "desde fuera" + // De manera que el valor de la fuente depende del orden en el que se ejecuten + // los test, y para que el test sea independiente de eso la verificación + // comprueba los dos posibles valores + assertEquals(Color.BLACK.toString(), font.getColor().toString(), + "No es el color de fuente especificado en el fichero de propiedades"); + assertEquals(10, font.getFont().getSize(), + "No es el tamaño de fuente especificado en el fichero de propiedades"); + assertEquals("Arial", font.getFont().getFamily(), + "No es el tipo de fuente especificado en el fichero de propiedades"); + + } catch (IOException e) { + fail("No debería devolver esta excepción"); + e.printStackTrace(); + } } /** * Test method for {@link us.muit.fs.a4i.config.Context#getMetricFont()}. + * + * @throws IOException */ + @DisplayName("Verificación lectura de configuración de fuente de métricas") @Test void testGetMetricFont() { - fail("Not yet implemented"); + try { + + Font font = null; + + font = Context.getContext().getMetricFont(); + assertNotNull(font, "No se ha inicializado bien la fuente"); + assertEquals(Color.GREEN.toString(), font.getColor().toString(), + "No es el color de fuente especificado en el fichero de propiedades"); + assertTrue(10 == font.getFont().getSize(), + "No es el tamaño de fuente especificado en el fichero de propiedades"); + assertEquals(java.awt.Font.SERIF, font.getFont().getFamily(), + "No es el tipo de fuente especificado en el fichero de propiedades"); + + } catch (IOException e) { + fail("No debería devolver esta excepción"); + e.printStackTrace(); + } } /** - * Test method for {@link us.muit.fs.a4i.config.Context#getIndicatorFont(us.muit.fs.a4i.model.entities.Indicator.State)}. + * Test method for + * {@link us.muit.fs.a4i.config.Context#getIndicatorFont(us.muit.fs.a4i.model.entities.Indicator.State)}. */ + @DisplayName("Verificación lectura de configuración de fuente de indicadores") @Test void testGetIndicatorFont() { - fail("Not yet implemented"); + try { + Font font = null; + + // Se le solicita la fuente del estado indefinido, que tendrá los valores por + // defecto Font.Default.xxx al no estar + // definidas sus propiedades en el fichero de configuración utilizados en los + // tests. + font = Context.getContext().getIndicatorFont(IndicatorState.OK); + assertNotNull(font, "No se ha inicializado bien la fuente"); + // El nombre o tipo de la fuente podrá ser Arial o Times según el momento en el + // que se realicen los tests. + log.info("familia " + font.getFont().getFamily()); + assertEquals("Serif", font.getFont().getFamily(), + "No es el tipo de fuente especificado en el fichero de propiedades"); + assertEquals(Color.BLACK, font.getColor(), + "No es el color de fuente especificado en el fichero de propiedades"); + assertEquals(10, font.getFont().getSize(), + "No es el tamaño de fuente especificado en el fichero de propiedades"); + + // Se le solicita al contexto la fuente del estao "CRITICAL", cuyas propiedades + // están definidas en el + // fichero de configuración por defecto. + font = Context.getContext().getIndicatorFont(IndicatorState.CRITICAL); + + assertNotNull(font, "No se ha inicializado bien la fuente"); + + log.info("Datos fuente estado CRITICAL familia " + font.getFont().getFamily() + " tamano " + + font.getFont().getSize() + " color " + font.getColor()); + + assertTrue(font.getFont().getFamily().equals("Arial"), + "No es el tipo de fuente especificado en el fichero de propiedades"); + assertEquals(Color.RED, font.getColor(), + "No es el color de fuente especificado en el fichero de propiedades"); + assertEquals(12, font.getFont().getSize(), + "No es el tamaño de fuente especificado en el fichero de propiedades"); + + } catch (IOException e) { + fail("No debería devolver esta excepción"); + e.printStackTrace(); + } } /** * Test method for {@link us.muit.fs.a4i.config.Context#getPropertiesNames()}. + * + * @throws IOException Este método de verificación está incompleto, deberá ser + * completado commo ejercicio Verificar que los nombres son + * correctos */ + @DisplayName("Verificación obtención nombre de propiedades configuradas") @Test - void testGetPropertiesNames() { - fail("Not yet implemented"); + void testGetPropertiesNames() throws IOException { + log.info(Context.getContext().getPropertiesNames().toString()); } } diff --git a/src/test/java/us/muit/fs/a4i/test/config/ContextTest2.java b/src/test/java/us/muit/fs/a4i/test/config/ContextTest2.java new file mode 100644 index 00000000..70df9b87 --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/config/ContextTest2.java @@ -0,0 +1,302 @@ +/** + * Separo los tests de Context en dos, para evitar el problema con el singleton: + * Si se modifican los ficheros de configuración los resultados son distintos, porque el orden de ejecución de los tests es aleatorio + */ +package us.muit.fs.a4i.test.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import us.muit.fs.a4i.model.entities.Font; // Clase que sustituye a java.awt.Font + +import java.awt.Color; +import java.awt.GraphicsEnvironment; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.logging.Logger; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import us.muit.fs.a4i.config.Context; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.Set; +import java.util.logging.Logger; + +import us.muit.fs.a4i.model.entities.IndicatorI; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.Set; +import java.util.logging.Logger; + +/** + * @author Isabel Román Verificación de la clase context cuando hay ficheros de + * configuración personalizados + */ +class ContextTest2 { + private static Logger log = Logger.getLogger(ContextTest2.class.getName()); + /** + * Ruta al fichero de configuración de indicadores y métricas establecidos por + * la aplicación + */ + static String appConfPath; + /** + * Ruta al fichero de configuración de propiedades de funcionamiento + * establecidos por la aplicación + */ + static String appPath; + + /** + * @throws java.lang.Exception Se establecen los ficheros de configuración de la + * api y de métricas e indicadores propietarios + */ + @BeforeAll + static void setUpBeforeClass() throws Exception { + log.info("Estableciendo las rutas de los ficheros de configuración del cliente"); + // Fichero de métricas e indicadores establecido por el cliente + appPath = "src" + File.separator + "test" + File.separator + "resources" + File.separator + "appConfTest.json"; + // Fichero de configuración de la api establecido por el cliente + appConfPath = "src" + File.separator + "test" + File.separator + "resources" + File.separator + "appTest.conf"; + Context.setAppConf(appConfPath); + Context.setAppRI(appPath); + } + + /** + * @throws java.lang.Exception + */ + @AfterAll + static void tearDownAfterClass() throws Exception { + } + + /** + * @throws java.lang.Exception + */ + @BeforeEach + void setUp() throws Exception { + } + + /** + * @throws java.lang.Exception + */ + @AfterEach + void tearDown() throws Exception { + // Ejecutar tras cada test + } + + /** + * Test method for {@link us.muit.fs.a4i.config.Context#getContext()}. + */ + @Test + void testGetContext() { + try { + assertNotNull(Context.getContext(), "Devuelve null"); + assertTrue(Context.getContext() instanceof us.muit.fs.a4i.config.Context, "No es del tipo apropiado"); + assertSame(Context.getContext(), Context.getContext(), "No se devuelve el mismo contexto siempre"); + + } catch (IOException e) { + fail("No debería lanzar esta excepción"); + e.printStackTrace(); + } + + } + + /** + * Test method for + * {@link us.muit.fs.a4i.config.Context#setAppConf(java.lang.String)}. + */ + @Test + @Tag("Integracion") + void testSetAppConf() { + + try { + + Context.setAppConf(appPath); + assertTrue(appPath.equals(Context.getAppConf()), + "No coincide la ruta del fichero de métricas con la configurada"); + + } catch (IOException e) { + fail("No se encuentra el fichero de especificación de métricas e indicadores"); + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * Test method for {@link us.muit.fs.a4i.config.Context#getChecker()}. + */ + @Test + void testGetChecker() { + try { + assertNotNull(Context.getContext().getChecker(), "No devuelve el checker"); + assertTrue(Context.getContext().getChecker() instanceof us.muit.fs.a4i.config.Checker, + "No es del tipo apropiado"); + + } catch (IOException e) { + fail("No debería devolver esta excepción"); + e.printStackTrace(); + } + } + + /** + * Test method for {@link us.muit.fs.a4i.config.Context#getPersistenceType()}. + */ + @Test + void testGetPersistenceType() { + try { + assertEquals("WORD", Context.getContext().getPersistenceType(), + "En el fichero de configuración personalizado está definido el tipo word"); + } catch (IOException e) { + fail("El fichero no se localiza"); + e.printStackTrace(); + } + } + + /** + * Test method for {@link us.muit.fs.a4i.config.Context#getRemoteType()}. + */ + @Test + void testGetRemoteType() { + try { + assertEquals("GITLAB", Context.getContext().getRemoteType(), + "En el fichero de configuración personalizado está el tipo gitlab"); + } catch (IOException e) { + fail("El fichero no se localiza"); + e.printStackTrace(); + } + } + + /** + *

+ * Este test permite verificar que se lee bien la fuente. Este método es + * idéntico al de la clase ContextTest porque el fichero personalizado no + * modifica los parámetros por defecto + *

+ * Test method for {@link us.muit.fs.a4i.config.Context#getDefaultFont()}. + * + */ + @Test + void testGetDefaultFont() { + try { + Font font = null; + String color; + // Uso esto para ver los tipos de fuentes de los que dispongo + // String[] fontNames = + // GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + // log.info("listado de fuentes " + Arrays.toString(fontNames)); + font = Context.getContext().getDefaultFont(); + assertNotNull(font, "No se ha inicializado bien la fuente"); + assertEquals(Color.BLACK.toString(), font.getColor().toString(), + "No es el color de fuente especificado en el fichero de propiedades"); + assertEquals(10, font.getFont().getSize(), + "No es el tamaño de fuente especificado en el fichero de propiedades"); + assertEquals("Arial", font.getFont().getFamily(), + "No es el tipo de fuente especificado en el fichero de propiedades"); + + } catch (IOException e) { + fail("No debería devolver esta excepción"); + e.printStackTrace(); + } + } + + /** + *

+ * Este test permite verificar si se sobreescribe la configuración por defecto + *

+ * Test method for {@link us.muit.fs.a4i.config.Context#getMetricFont()}. + * + * @throws IOException + */ + @Test + + void testGetMetricFont() { + try { + + Font font = null; + // Uso esto para ver los tipos de fuentes de los que dispongo + // String[] fontNames = + // GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + // log.info("listado de fuentes " + Arrays.toString(fontNames)); + font = Context.getContext().getMetricFont(); + assertNotNull(font, "No se ha inicializado bien la fuente"); + assertEquals(Color.RED.toString(), font.getColor().toString(), + "No es el color de fuente especificado en el fichero de propiedades"); + assertTrue(15 == font.getFont().getSize(), + "No es el tamaño de fuente especificado en el fichero de propiedades"); + assertEquals("Arial", font.getFont().getFamily(), + "No es el tipo de fuente especificado en el fichero de propiedades"); + + } catch (IOException e) { + fail("No debería devolver esta excepción"); + e.printStackTrace(); + } + } + + /** + * Test method for + * {@link us.muit.fs.a4i.config.Context#getIndicatorFont(us.muit.fs.a4i.model.entities.Indicator.State)}. + */ + @Test + void testGetIndicatorFont() { + try { + Font font = null; + + // Se le solicita la fuente del estado indefinido, que tendrá los valores por + // defecto al no estar + // definidas sus propiedades en el fichero de configuración utilizados en los + // tests. + + font = Context.getContext().getIndicatorFont(IndicatorState.UNDEFINED); + assertNotNull(font, "No se ha inicializado bien la fuente"); + // El nombre o tipo de la fuente podrá ser Arial o Times según el momento en el + // que se realicen los tests. + assertEquals("Arial", font.getFont().getFamily(), + "No es el tipo de fuente especificado en el fichero de propiedades"); + + // Se le solicita al contexto la fuente del estao "CRITICAL" + font = Context.getContext().getIndicatorFont(IndicatorState.CRITICAL); + assertNotNull(font, "No se ha inicializado bien la fuente"); + assertEquals("Courier New", font.getFont().getFamily(), + "No es el tipo de fuente especificado en el fichero de propiedades"); + assertEquals(Color.RED.toString(), font.getColor().toString(), + "No es el color de fuente especificado en el fichero de propiedades"); + assertEquals(20, font.getFont().getSize(), + "No es el tamaño de fuente especificado en el fichero de propiedades"); + + } catch (IOException e) { + fail("No debería devolver esta excepción"); + e.printStackTrace(); + } + } + + /** + * Test method for {@link us.muit.fs.a4i.config.Context#getPropertiesNames()}. + * + * @throws IOException Este método de verificación está incompleto, deberá ser + * completado commo ejercicio Verificar que los nombres son + * correctos + */ + @Test + void testGetPropertiesNames() throws IOException { + log.info(Context.getContext().getPropertiesNames().toString()); + } + +} diff --git a/src/test/java/us/muit/fs/a4i/test/config/IndicatorConfigurationTest.java b/src/test/java/us/muit/fs/a4i/test/config/IndicatorConfigurationTest.java new file mode 100644 index 00000000..ab6dfe53 --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/config/IndicatorConfigurationTest.java @@ -0,0 +1,257 @@ +/** + * + */ +package us.muit.fs.a4i.test.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.HashMap; +import java.util.logging.Logger; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import us.muit.fs.a4i.config.IndicatorConfiguration; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.Indicator; +import us.muit.fs.a4i.model.entities.IndicatorI; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItem.ReportItemBuilder; +import us.muit.fs.a4i.model.entities.ReportItemI; + +/** + * Clase para verificar IndicatorConfiguration cuando se usa el fichero de + * configuración de indicadores por defecto y además el personalizado por el + * cliente + */ +class IndicatorConfigurationTest { + private static Logger log = Logger.getLogger(IndicatorConfigurationTest.class.getName()); + static IndicatorConfiguration underTest; + static String appConfPath; + private static String defaultFile = "a4iDefault.json"; + String appIndicatorsPath = "/test/home"; + + /** + *

+ * Acciones a realizar antes de ejecutar los tests definidos en esta clase. Se + * va a crear un objeto IndicatorConfiguration + *

+ * + * @throws java.lang.Exception + * @see org.junit.jupiter.api.BeforeAll + */ + @BeforeAll + static void setUpBeforeClass() throws Exception { + appConfPath = "src" + File.separator + "test" + File.separator + "resources" + File.separator + + "appConfTest.json"; + underTest = new IndicatorConfiguration(defaultFile, appConfPath); + } + + /** + *

+ * Acciones a realizar después de ejecutar todos los tests de esta clase + *

+ * + * @throws java.lang.Exception + * @see org.junit.jupiter.api.AfterAll + */ + @AfterAll + static void tearDownAfterClass() throws Exception { + log.info("He ejectuado todos los test definidos en esta clase"); + } + + /** + * Acciones a realizar después de cada uno de los tests de esta clase + * + * @throws java.lang.Exception + * @see org.junit.jupiter.api.AfterEach + */ + @AfterEach + void tearDown() throws Exception { + log.info("Acabo de ejecutar un test definido en esta clase"); + } + + @DisplayName("Verifica lectura fichero de configuración por defecto") + @Test + void testDefinedIndicator() { + // Creo un par de variables, que me servirán de valores para verificar si + // comprueba bien el tipo + // Las métricas del test son de tipo entero, así que creo un entero y un string + // (el primero no dará problemas el segundo sí) + Double valOKMock = Double.valueOf(0.3); + String valKOMock = "KO"; + HashMap returnedMap = null; + // Primero, sin fichero de configuración de aplicación + try { + + // Consulta un indicador no definido, con valor de tipo entero + // debe devolver null, no está definido + log.info("Busco el indicador llamado noexiste"); + returnedMap = underTest.definedIndicator("noexiste", valOKMock.getClass().getName()); + assertNull(returnedMap, "Debería ser nulo, el indicador noexiste no está definido"); + + /* + * Indicador que existe en el fichero de configuración por defecto { "name": + * "issuesProgress", "type": "java.lang.Double", "description": + * "Ratio de issues cerrados frente a totales", "unit": "ratio", "limits": { + * "ok": 2, "warning": 4, "critical": 6 } } + */ + // Busco el indicador overdued con valor double, no debería dar problemas + log.info("Busco el indicador issuesProgress"); + returnedMap = underTest.definedIndicator("issuesProgress", valOKMock.getClass().getName()); + assertNotNull(returnedMap, "Debería devolver un hashmap, el indicador issuesProgress está definido"); + assertTrue(returnedMap.containsKey("unit"), "La clave unit tiene que estar en el mapa"); + assertTrue(returnedMap.containsKey("description"), "La clave description tiene que estar en el mapa"); + // Se comprueba que los indicadores incluyen los limites definidos + assertTrue(returnedMap.containsKey("limits.ok"), + "La clave correspondiente al limite del estado OK tiene que estar en el mapa"); + assertTrue(returnedMap.containsKey("limits.warning"), + "La clave correspondiente al limite del estado WARNING tiene que estar en el mapa"); + assertTrue(returnedMap.containsKey("limits.critical"), + "La clave correspondiente al limite del estado CRITICAL tiene que estar en el mapa"); + // Busco una métrica que existe pero con un tipo incorrecto + assertNull(underTest.definedIndicator("overdued", valKOMock.getClass().getName()), + "Debería ser nulo, el indicador overdued está definido para Double"); + } catch (FileNotFoundException e) { + fail("El fichero está en la carpeta resources"); + e.printStackTrace(); + } + } + + /** + * Verifico que encuentra el indicador en el fichero propietario, y que si este + * fichero no existe lanza la excepción adecuada + */ + @DisplayName("Verifica lectura fichero de configuración propietario") + @Test + void testDefinedIndicatorCustom() { + // Creo un par de variables, que me servirán de valores para verificar si + // comprueba bien el tipo + // Las métricas del test son de tipo entero, así que creo un entero y un string + // (el primero no dará problemas el segundo sí) + Double valOKMock = Double.valueOf(0.3); + String valKOMock = "KO"; + HashMap returnedMap = null; + try { + + // Busco una métrica que se que no está en la configuración de la api pero + // sí en la de la aplicación + log.info("Busco el indicador llamado pullReqGlory"); + returnedMap = underTest.definedIndicator("pullReqGlory", valOKMock.getClass().getName()); + assertNotNull(returnedMap, "Debería devolver un hashmap, el indicador está definido"); + assertTrue(returnedMap.containsKey("unit"), "La clave unit tiene que estar en el mapa"); + assertTrue(returnedMap.containsKey("description"), "La clave description tiene que estar en el mapa"); + // Se comprueba que los indicadores incluyen los limites definidos + assertTrue(returnedMap.containsKey("limits.ok"), + "La clave correspondiente al limite del estado OK tiene que estar en el mapa"); + assertTrue(returnedMap.containsKey("limits.warning"), + "La clave correspondiente al limite del estado WARNING tiene que estar en el mapa"); + assertTrue(returnedMap.containsKey("limits.critical"), + "La clave correspondiente al limite del estado CRITICAL tiene que estar en el mapa"); + } catch (FileNotFoundException e) { + fail("No debería devolver esta excepción"); + } catch (Exception e) { + fail("Lanza una excepción no reconocida " + e); + } + + } + + /** + *

+ * En este test verifico que si busco el nombre de una métrica el método que + * verifica el indicador no lo confunde + *

+ */ + @Test + void testDefinedIndicatorAsMetric() { + Integer valOKMock = Integer.valueOf(3); + + HashMap returnedMap = null; + + try { + // Consulta el nombre de un indicador que en realidad es una métrica + log.info("Busco el indicador llamado pullReqGlory"); + returnedMap = underTest.definedIndicator("subscribers", valOKMock.getClass().getName()); + assertNull(returnedMap, "Debería ser nulo, subscribers es una métrica y no un indicador no está definido"); + } catch (Exception e) { + fail("Lanza la excepción " + e); + } + } + + @Test + void testListAllIndicators() { + /* + * En el momento de codificar este test (21/3/25) el número de indicadores en el + * fichero de configuración por defecto es 9 y en el de la aplicación 2 + */ + try { + assertEquals(11, underTest.listAllIndicators().size(), "El número de indicadores no es el esperado"); + } catch (FileNotFoundException e) { + fail("No debería lanzar esta excepción"); + e.printStackTrace(); + } + } + + @Test + void testGetIndicatorState() { + ReportItemI indicator = null; + IndicatorI.IndicatorState estado = IndicatorI.IndicatorState.UNDEFINED; + + try { + indicator = new ReportItemBuilder("overdued", 2.0).build(); + } catch (ReportItemException e) { + fail("El archivo de configuración esperado no contiene el indicador necesario para esta prueba."); + } + + estado = underTest.getIndicatorState(indicator); + + assertTrue(estado == IndicatorI.IndicatorState.OK, "El estado es INCORRECTO. Debería de ser OK."); + + try { + indicator = new ReportItemBuilder("overdued", 9.0).build(); + } catch (ReportItemException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + estado = underTest.getIndicatorState(indicator); + + assertTrue(estado == IndicatorI.IndicatorState.WARNING, "El estado es INCORRECTO. Debería de ser WARNING."); + + try { + indicator = new ReportItemBuilder("overdued", 13.0).build(); + } catch (ReportItemException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + estado = underTest.getIndicatorState(indicator); + + assertTrue(estado == IndicatorI.IndicatorState.CRITICAL, "El estado es INCORRECTO. Debería de ser CRITICAL."); + + try { + indicator = new ReportItemBuilder("issuesRatio", 13.0).build(); + } catch (ReportItemException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + estado = underTest.getIndicatorState(indicator); + + assertTrue(estado == IndicatorI.IndicatorState.UNDEFINED, "El estado es INCORRECTO. Debería de ser UNDEFINED."); + + } + +} diff --git a/src/test/java/us/muit/fs/a4i/test/config/IndicatorConfigurationTest2.java b/src/test/java/us/muit/fs/a4i/test/config/IndicatorConfigurationTest2.java new file mode 100644 index 00000000..6c2e0f4b --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/config/IndicatorConfigurationTest2.java @@ -0,0 +1,86 @@ +/** + * + */ +package us.muit.fs.a4i.test.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Logger; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import us.muit.fs.a4i.config.Context; +import us.muit.fs.a4i.config.IndicatorConfiguration; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.Indicator; +import us.muit.fs.a4i.model.entities.IndicatorI; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItem.ReportItemBuilder; +import us.muit.fs.a4i.model.entities.ReportItemI; + +/** + * Clase para verificar IndicatorConfiguration cuando se configura un fichero de + * cliente que no existe + */ +class IndicatorConfigurationTest2 { + private static Logger log = Logger.getLogger(IndicatorConfigurationTest2.class.getName()); + static IndicatorConfiguration underTest; + static String appConfPath; + private static String defaultFile = "a4iDefault.json"; + String appIndicatorsPath = "/test/home"; + + /** + *

+ * Acciones a realizar antes de ejecutar los tests definidos en esta clase. Se + * va a crear un objeto IndicatorConfiguration + *

+ * + * @throws java.lang.Exception + * @see org.junit.jupiter.api.BeforeAll + */ + @BeforeAll + static void setUpBeforeClass() throws Exception { + appConfPath = "src" + File.separator + "test" + File.separator + "resources" + File.separator + + "appConfTestKO.json"; + underTest = new IndicatorConfiguration(defaultFile, appConfPath); + } + + /** + * Verifico que si el fichero no existe lanza la excepción adecuada + */ + @DisplayName("Intento de acceso a fichero que no existe") + @Test + void testDefinedIndicatorCustom() { + + List indicadores = null; + try { + + // Busco una métrica que se que no está en la configuración de la api pero + // sí en la de la aplicación + log.info("Consulto los indicadores"); + indicadores = underTest.listAllIndicators(); + fail("Debería haber devuelto una excepción"); + + } catch (FileNotFoundException e) { + assertNotNull(e, "Debería haber recibido una excepción"); + } catch (Exception e) { + fail("Lanza una excepción no reconocida " + e); + } + } + +} diff --git a/src/test/java/us/muit/fs/a4i/test/config/MetricConfigurationTest.java b/src/test/java/us/muit/fs/a4i/test/config/MetricConfigurationTest.java new file mode 100644 index 00000000..b2d8082e --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/config/MetricConfigurationTest.java @@ -0,0 +1,311 @@ +/** + * + */ +package us.muit.fs.a4i.test.config; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Logger; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import us.muit.fs.a4i.config.IndicatorConfiguration; +import us.muit.fs.a4i.config.MetricConfiguration; +import us.muit.fs.a4i.config.MetricConfigurationI; + +/** + * Verificación de la clase MetricConfiguration si sólo se usa el fichero de + * ocnfiguración por defecto + */ +class MetricConfigurationTest { + + private static Logger log = Logger.getLogger(MetricConfigurationTest.class.getName()); + private static MetricConfigurationI underTest; + static String appConfPath; + private static String defaultFile = "a4iDefault.json"; + + /** + * @throws java.lang.Exception Crea el objeto bajo test únicamente con el + * fichero de configuración por defecto + */ + @BeforeEach + void setUp() throws Exception { + + underTest = new MetricConfiguration(defaultFile, null); + } + + /** + *

+ * Test para verificar el método + * {@link us.muit.fs.a4i.config.MetricConfiguration#definedMetric(java.lang.String, java.lang.String)}. + * Si la métrica no está definida no debería devolver el mapa clave las + * etiquetas: + * + * Las métricas pueden estar definidas en el fichero de configuración de la api + * (a4iDefault.json) o en otro fichero configurado por la aplicación cliente. + * Para los test este fichero es appConfTest.json y se guarda junto al código de + * test, en la carpeta resources + * + * @see org.junit.jupiter.api.Tag + * @see org.junit.jupiter.api.Test + * @see org.junit.jupiter.api.DisplayName * + *

+ */ + @Tag("unidad") + @DisplayName("Verificación definedMetric si la métrica no existe en el fichero de configuración por defecto") + @Test + void testDefinedMetric1() { + + // Las métricas del test son de enteros, así que creo un entero y un string (el + // primero no dará problemas el segundo sí + Integer valOKMock = Integer.valueOf(3); + String valKOMock = "KO"; + HashMap returnedMap = null; + // Primero, sin fichero de configuraci�n de aplicaci�n + try { + // Busco una métrica que se que no está en la configuración de la api + log.info("Busco la métrica llamada downloads"); + returnedMap = underTest.definedMetric("downloads", valOKMock.getClass().getName()); + assertNull(returnedMap, + "Debería ser nulo, la métrica downloads no está definida en el fichero de configuración por defecto"); + } catch (FileNotFoundException e) { + fail("El fichero NO está en la carpeta resources"); + e.printStackTrace(); + } + + } + + /** + *

+ * Test para verificar el método + * {@link us.muit.fs.a4i.config.MetricConfiguration#definedMetric(java.lang.String, java.lang.String)}. + * Si la métrica está definida y el tipo de valor que se quiere establecer es el + * adecuado debe devolver un hashmap con los datos de la métrica, usando como + * clave las etiquetas: + *

    + *
  • description
  • + *
  • unit
  • + *
+ * Las métricas pueden estar definidas en el fichero de configuración de la api + * (a4iDefault.json) o en otro fichero configurado por la aplicación cliente. + * Para los test este fichero es appConfTest.json y se guarda junto al código de + * test, en la carpeta resources + * + * @see org.junit.jupiter.api.Tag + * @see org.junit.jupiter.api.Test + * @see org.junit.jupiter.api.DisplayName + *

+ */ + @Tag("unidad") + @DisplayName("Verificación definedMetric si la métrica existe en el fichero de configuración por defecto y el tipo es correcto") + @Test + void testDefinedMetric2() { + // Creo valores Mock para verificar si comprueba bien el tipo + // Las m�tricas del test son de enteros, as� que creo un entero y un string (el + // primero no dar� problemas el segundo sí) + Integer valOKMock = Integer.valueOf(3); + + HashMap returnedMap = null; + // Primero, sin fichero de configuraci�n de aplicaci�n + try { + + /* + * En el fichero por defecto la métrica issues está definida del siguiente modo + * { "name": "issues", "type": "java.lang.Integer", "description": + * "Tareas totales", "unit": "issues" } + */ + // Primero se busca la métrica con el tipo definido + HashMap metricInfo = underTest.definedMetric("issues", valOKMock.getClass().getName()); + assertEquals("issues", metricInfo.get("name"), "No se ha leído bien el nombre de la métrica"); + assertEquals("java.lang.Integer", metricInfo.get("type"), "No se ha leído bien el tipo de la métrica"); + assertEquals("Tareas totales", metricInfo.get("description"), + "No se ha leído bien la descripción de la métrica"); + assertEquals(metricInfo.get("unit"), "issues", "No se ha leído bien las unidades de la métrica"); + + } catch (FileNotFoundException e) { + fail("El fichero NO está en la carpeta resources"); + e.printStackTrace(); + } + + } + + /** + *

+ * Test para verificar el método + * {@link us.muit.fs.a4i.config.MetricConfiguration#definedMetric(java.lang.String, java.lang.String)}. + * Si la métrica está definida pero el tipo de valor no es el adecuado no debe + * devolver el mapa Las métricas pueden estar definidas en el fichero de + * configuración de la api (a4iDefault.json) o en otro fichero configurado por + * la aplicación cliente. Para los test este fichero es appConfTest.json y se + * guarda junto al código de test, en la carpeta resources + * + * @see org.junit.jupiter.api.Tag + * @see org.junit.jupiter.api.Test + * @see org.junit.jupiter.api.DisplayName * + *

+ */ + @Tag("unidad") + @DisplayName("Verificación definedMetric si la métrica existe en el fichero de configuración por defecto pero el tipo es incorrecto") + @Test + void testDefinedMetric3() { + // Creo valores Mock para verificar si comprueba bien el tipo + // Las m�tricas del test son de enteros, as� que creo un entero y un string (el + // primero no dar� problemas el segundo sí) + Integer valOKMock = Integer.valueOf(3); + String valKOMock = "KO"; + HashMap returnedMap = null; + // Primero, sin fichero de configuraci�n de aplicaci�n + try { + + /* + * En el fichero por defecto la métrica issues está definida del siguiente modo + * { "name": "issues", "type": "java.lang.Integer", "description": + * "Tareas totales", "unit": "issues" } + */ + // Busco la métrica con tipo incorrecto + HashMap metricInfo = underTest.definedMetric("issues", valKOMock.getClass().getName()); + assertNull(metricInfo, "La métrica no tiene el tipo correcto, no debería devolver el mapa"); + + } catch (FileNotFoundException e) { + fail("El fichero NO está en la carpeta resources"); + e.printStackTrace(); + } + + } + + /** + * Test method for + * {@link us.muit.fs.a4i.config.MetricConfiguration#getMetricInfo(java.lang.String)}. + * Si la métrica no está configurada no debe devolver el mapa + */ + @Tag("unidad") + @DisplayName("Verificación de getMetricInfo si la métrica no existe en el fichero de configuración") + @Test + void testGetMetricInfo1() { + HashMap returnedMap = null; + + // Las métricas del test son de enteros, así que creo un entero y un string (el + // primero no dará problemas el segundo sí + Integer valOKMock = Integer.valueOf(3); + String valKOMock = "KO"; + // Primero, sin fichero de configuraci�n de aplicaci�n + try { + // Busco una métrica que se que no está en la configuración de la api + log.info("Busco la métrica llamada downloads"); + returnedMap = underTest.getMetricInfo("downloads"); + assertNull(returnedMap, + "Debería ser nulo, la métrica downloads no está definida en el fichero de configuración por defecto"); + } catch (FileNotFoundException e) { + fail("El fichero NO está en la carpeta resources"); + e.printStackTrace(); + } + + } + + /** + * Test method for + * {@link us.muit.fs.a4i.config.MetricConfiguration#definedMetric(java.lang.String, java.lang.String)}. + * Si la métrica existe debe devolver el mapa con los valores + */ + @Tag("unidad") + @DisplayName("Verificación getMetricInfo si la métrica existe en el fichero de configuración por defecto") + @Test + void testGetMetricInfo2() { + HashMap returnedMap = null; + + try { + + /* + * En el fichero por defecto la métrica issues está definida del siguiente modo + * { "name": "issues", "type": "java.lang.Integer", "description": + * "Tareas totales", "unit": "issues" } + */ + // Primero se busca la métrica con el tipo definido + HashMap metricInfo = underTest.getMetricInfo("issues"); + assertEquals("issues", metricInfo.get("name"), "No se ha leído bien el nombre de la métrica"); + assertEquals("java.lang.Integer", metricInfo.get("type"), "No se ha leído bien el tipo de la métrica"); + assertEquals("Tareas totales", metricInfo.get("description"), + "No se ha leído bien la descripción de la métrica"); + assertEquals(metricInfo.get("unit"), "issues", "No se ha leído bien las unidades de la métrica"); + + } catch (FileNotFoundException e) { + fail("El fichero NO está en la carpeta resources"); + e.printStackTrace(); + } + + } + + /** + * Test method for + * {@link us.muit.fs.a4i.config.MetricConfiguration#listAllMetrics()}. El número + * de métricas debe coincidir con las que haya en el fichero de configuración + */ + @Tag("unidad") + @DisplayName("Verificación de consulta de métricas para el fichero de configuración por defecto") + @Test + void testListAllMetrics() { + List metricsList; + try { + metricsList = underTest.listAllMetrics(); + /** + * En el momento de codificar este test 21/3/25 el número de métricas en el + * fichero de configuración por defecto es 39 + */ + assertEquals(39, metricsList.size(), "No lee correctamente las métricas"); + } catch (FileNotFoundException e) { + fail("No debería lanzar esta excepción"); + e.printStackTrace(); + } + + } + + /** + *

+ * Test para verificar que se lanza adecuadamente la excepción de fichero no + * localizado en todos los métodos + * + * @see org.junit.jupiter.api.Tag + * @see org.junit.jupiter.api.Test + * @see org.junit.jupiter.api.DisplayName + *

+ */ + @DisplayName("Verificación de excepción FileNotFound cuando el fichero por defecto no está bien indicado") + @Test + void testExceptionFile() { + underTest = new MetricConfiguration("defaultKO", appConfPath); + /* + * el fichero por defecto no existe, pruebo los tres métodos + */ + FileNotFoundException thrown = assertThrows(FileNotFoundException.class, + + () -> underTest.getMetricInfo("downloads"), + "Debería haber lanzado la excepción de fichero no encontrado, pero no lo ha hecho"); + + assertTrue(thrown.getMessage().contains("defaultKO"), "La excepción debería indicar el fichero no localizado"); + + thrown = assertThrows(FileNotFoundException.class, + + () -> underTest.definedMetric("downloads", "java.lang.Integer"), + "Debería haber lanzado la excepción de fichero no encontrado, pero no lo ha hecho"); + + assertTrue(thrown.getMessage().contains("defaultKO"), "La excepción debería indicar el fichero no localizado"); + + thrown = assertThrows(FileNotFoundException.class, + + () -> underTest.listAllMetrics(), + "Debería haber lanzado la excepción de fichero no encontrado, pero no lo ha hecho"); + + assertTrue(thrown.getMessage().contains("defaultKO"), "La excepción debería indicar el fichero no localizado"); + + } + +} diff --git a/src/test/java/us/muit/fs/a4i/test/config/MetricConfigurationTest2.java b/src/test/java/us/muit/fs/a4i/test/config/MetricConfigurationTest2.java new file mode 100644 index 00000000..33b37cca --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/config/MetricConfigurationTest2.java @@ -0,0 +1,325 @@ +/** + * + */ +package us.muit.fs.a4i.test.config; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Logger; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import us.muit.fs.a4i.config.IndicatorConfiguration; +import us.muit.fs.a4i.config.MetricConfiguration; +import us.muit.fs.a4i.config.MetricConfigurationI; + +/** + * En esta clase se verifica la clase MetricConfiguration pero usando un fichero + * de configuración de métricas de la aplicación cliente + */ +class MetricConfigurationTest2 { + + private static Logger log = Logger.getLogger(MetricConfigurationTest2.class.getName()); + private static MetricConfigurationI underTest; + static String appConfPath; + private static String defaultFile = "a4iDefault.json"; + String appIndicatorsPath = "/test/home"; + + /** + * @throws java.lang.Exception + */ + @BeforeAll + static void setUpBeforeClass() throws Exception { + appConfPath = "src" + File.separator + "test" + File.separator + "resources" + File.separator + + "appConfTest.json"; + + } + + /** + * Test method for + * {@link us.muit.fs.a4i.config.MetricConfiguration#definedMetric(java.lang.String, java.lang.String)}. + * En este caso se usa un único métdo para verificar todos los casos + *

+ * Test para verificar el método + * {@link us.muit.fs.a4i.config.MetricConfiguration#definedMetric(java.lang.String, java.lang.String)}. + * Si la métrica está definida y el tipo de valor que se quiere establecer es el + * adecuado debe devolver un hashmap con los datos de la métrica, usando como + * clave las etiquetas: + *

    + *
  • description
  • + *
  • unit
  • + *
+ * Pero si no está definida no debe crear el mapa Las métricas pueden estar + * definidas en el fichero de configuración de la api (a4iDefault.json) o en + * otro fichero configurado por la aplicación cliente. Para los test este + * fichero es appConfTest.json y se guarda junto al código de test, en la + * carpeta resources + * + * @see org.junit.jupiter.api.Tag + * @see org.junit.jupiter.api.Test + * @see org.junit.jupiter.api.DisplayName + *

+ */ + @Tag("unidad") + @DisplayName("Verificación del método definedMetric") + @Test + void testDefinedMetric() { + underTest = new MetricConfiguration(defaultFile, appConfPath); + // Creo valores Mock para verificar si comprueba bien el tipo + // Las m�tricas del test son de enteros, as� que creo un entero y un string (el + // primero no dar� problemas el segundo sí) + Integer valOKMock = Integer.valueOf(3); + String valKOMock = "KO"; + HashMap returnedMap = null; + // Primero, sin fichero de configuraci�n de aplicaci�n + try { + // Consulta una m�trica no definida, con valor de tipo entero + // debe devolver null, no est� definida + log.info("Busco la métrica llamada NoExiste"); + returnedMap = underTest.definedMetric("NoExiste", valOKMock.getClass().getName()); + assertNull(returnedMap, "Debería ser nulo, la métrica NoExiste no está definida"); + /* + * En el fichero por defecto la métrica issues está definida del siguiente modo + * { "name": "issues", "type": "java.lang.Integer", "description": + * "Tareas totales", "unit": "issues" } + */ + // Primero se busca la métrica con el tipo definido + HashMap metricInfo = underTest.definedMetric("issues", "java.lang.Integer"); + assertEquals("issues", metricInfo.get("name"), "No se ha leído bien el nombre de la métrica"); + assertEquals("java.lang.Integer", metricInfo.get("type"), "No se ha leído bien el tipo de la métrica"); + assertEquals("Tareas totales", metricInfo.get("description"), + "No se ha leído bien la descripción de la métrica"); + assertEquals(metricInfo.get("unit"), "issues", "No se ha leído bien las unidades de la métrica"); + + // Busco la métrica watchers con valor entero + log.info("Busco la métrica watchers"); + returnedMap = underTest.definedMetric("watchers", valOKMock.getClass().getName()); + assertNotNull(returnedMap, "Debería devolver un hashmap, la métrica está definida"); + assertTrue(returnedMap.containsKey("unit"), "La clave unit tiene que estar en el mapa"); + assertTrue(returnedMap.containsKey("description"), "La clave description tiene que estar en el mapa"); + + // Busco la métrica watchers con un tipo incorrecto + assertNull(underTest.definedMetric("watchers", valKOMock.getClass().getName()), + "Debería ser nulo, la métrica está definida para Integer"); + // Busco una métrica que se que no está en la configuración de la api pero sí en + // la de la aplicación + log.info("Busco la métrica llamada downloads"); + returnedMap = underTest.definedMetric("downloads", valOKMock.getClass().getName()); + assertNotNull(returnedMap, "Debería devolver un hashmap, la métrica está definida"); + assertTrue(returnedMap.containsKey("unit"), "La clave unit tiene que estar en el mapa"); + assertTrue(returnedMap.containsKey("description"), "La clave description tiene que estar en el mapa"); + } catch (FileNotFoundException e) { + fail("El fichero NO está en la carpeta resources"); + e.printStackTrace(); + } + + } + + /** + * Test method for + * {@link us.muit.fs.a4i.config.MetricConfiguration#definedMetric(java.lang.String, java.lang.String)}. + * /** + *

+ * Test para verificar el método + * {@link us.muit.fs.a4i.config.MetricConfiguration#metricInfo(java.lang.String)}. + * Si la métrica está definida en el fichero de configuración del cliente debe + * devolver un hashmap con los datos de la métrica, usando como clave las + * etiquetas: + *

    + *
  • description
  • + *
  • unit
  • + *
+ * Pero si no está definida no debe crear el mapa Las métricas pueden estar + * definidas en el fichero de configuración de la api (a4iDefault.json) o en + * otro fichero configurado por la aplicación cliente. Para los test este + * fichero es appConfTest.json y se guarda junto al código de test, en la + * carpeta resources + * + * @see org.junit.jupiter.api.Tag + * @see org.junit.jupiter.api.Test + * @see org.junit.jupiter.api.DisplayName + *

+ */ + @Tag("unidad") + @DisplayName("Verificación de lectura métrica disponible en configuración de la aplicación") + @Test + void testGetMetricInfo1() { + underTest = new MetricConfiguration(defaultFile, appConfPath); + HashMap returnedMap; + try { + // Busco una métrica que se que no está en la configuración de la api pero sí en + // la de la aplicación + log.info("Busco la métrica llamada downloads"); + returnedMap = underTest.getMetricInfo("downloads"); + assertNotNull(returnedMap, "Debería devolver un hashmap, la métrica está definida"); + assertTrue(returnedMap.containsKey("unit"), "La clave unit tiene que estar en el mapa"); + assertTrue(returnedMap.containsKey("description"), "La clave description tiene que estar en el mapa"); + + } catch (FileNotFoundException e) { + fail("no debería devolver esta excepción"); + e.printStackTrace(); + } + } + + /** + * Test method for + * {@link us.muit.fs.a4i.config.MetricConfiguration#listAllMetrics()}. Debe + * devolver una lista con todas las métricas, en ambos ficheros + */ + @Tag("unidad") + @DisplayName("Verificación de consulta de métricas con los dos ficheros de configuración") + @Test + void testListAllMetrics() { + underTest = new MetricConfiguration(defaultFile, appConfPath); + List metricsList; + try { + metricsList = underTest.listAllMetrics(); + /** + * En el momento de codificar este test 21/3/25 el número de métricas totales es + * 41 (39 de configuración + 2 del cliente) + */ + assertEquals(41, metricsList.size(), "No lee correctamente las métricas"); + } catch (FileNotFoundException e) { + fail("No debería lanzar esta excepción"); + e.printStackTrace(); + } + + } + + /** + * Test method for + * {@link us.muit.fs.a4i.config.MetricConfiguration#definedMetric(java.lang.String, java.lang.String)}. + * /** + *

+ * Test para verificar el método + * {@link us.muit.fs.a4i.config.MetricConfiguration#metricInfo(java.lang.String)}. + * Si la métrica está definida en el fichero por defecto debe devolver un + * hashmap con los datos de la métrica, usando como clave las etiquetas: + *

    + *
  • description
  • + *
  • unit
  • + *
+ * Pero si no está definida no debe crear el mapa Las métricas pueden estar + * definidas en el fichero de configuración de la api (a4iDefault.json) o en + * otro fichero configurado por la aplicación cliente. Para los test este + * fichero es appConfTest.json y se guarda junto al código de test, en la + * carpeta resources + * + * @see org.junit.jupiter.api.Tag + * @see org.junit.jupiter.api.Test + * @see org.junit.jupiter.api.DisplayName + *

+ */ + @Tag("unidad") + @DisplayName("Verificación de lectura métrica disponible en configuración por defecto") + @Test + void testGetMetricInfo2() { + underTest = new MetricConfiguration(defaultFile, appConfPath); + try { + /* + * En el fichero por defecto la métrica issues está definida del siguiente modo + * { "name": "issues", "type": "java.lang.Integer", "description": + * "Tareas totales", "unit": "issues" } + */ + // Primero se busca una métrica que existe + HashMap metricInfo = underTest.getMetricInfo("issues"); + assertEquals("issues", metricInfo.get("name"), "No se ha leído bien el nombre de la métrica"); + assertEquals("java.lang.Integer", metricInfo.get("type"), "No se ha leído bien el tipo de la métrica"); + assertEquals("Tareas totales", metricInfo.get("description"), + "No se ha leído bien la descripción de la métrica"); + assertEquals("issues", metricInfo.get("unit"), "No se ha leído bien las unidades de la métrica"); + } catch (IOException e) { + + e.printStackTrace(); + fail("No debería devolver esta excepción"); + } + } + + /** + * Test method for + * {@link us.muit.fs.a4i.config.MetricConfiguration#definedMetric(java.lang.String, java.lang.String)}. + * /** + *

+ * Test para verificar el método + * {@link us.muit.fs.a4i.config.MetricConfiguration#definedMetric(java.lang.String, java.lang.String)}. + * Si la métrica no está definida no debe crear el mapa Las métricas pueden + * estar definidas en el fichero de configuración de la api (a4iDefault.json) o + * en otro fichero configurado por la aplicación cliente. Para los test este + * fichero es appConfTest.json y se guarda junto al código de test, en la + * carpeta resources + * + * @see org.junit.jupiter.api.Tag + * @see org.junit.jupiter.api.Test + * @see org.junit.jupiter.api.DisplayName + *

+ */ + @DisplayName("Verificación de lectura métrica no existente") + @Test + void testGetMetricInfo3() { + underTest = new MetricConfiguration(defaultFile, appConfPath); + try { + /* + * En el fichero por defecto la métrica noexiste no existe + */ + + HashMap metricInfo = underTest.getMetricInfo("noexiste"); + assertNull(metricInfo, "El mapa no debe haberse creado"); + } catch (IOException e) { + fail("Lanza excepcion indebida, no localiza el fichero"); + e.printStackTrace(); + + } + } + + /** + *

+ * Test para verificar que se lanza adecuadamente la excepción de fichero no + * localizado en todos los métodos + * + * @see org.junit.jupiter.api.Tag + * @see org.junit.jupiter.api.Test + * @see org.junit.jupiter.api.DisplayName + *

+ */ + @DisplayName("Verificación de excepción FileNotFound cuando el fichero de configuración del cliente no está bien especificado") + @Test + void testExceptionFile() { + + underTest = new MetricConfiguration(defaultFile, "clienteKO"); + /* + * el fichero de la api cliente no existe, pruebo los tres métodos + */ + FileNotFoundException thrown = assertThrows(FileNotFoundException.class, + + () -> underTest.definedMetric("downloads", "java.lang.Integer"), + "Debería haber lanzado la excepción de fichero no encontrado, pero no lo ha hecho"); + + assertTrue(thrown.getMessage().contains("clienteKO"), "La excepción debería indicar el fichero no localizado"); + + thrown = assertThrows(FileNotFoundException.class, + + () -> underTest.listAllMetrics(), + "Debería haber lanzado la excepción de fichero no encontrado, pero no lo ha hecho"); + + assertTrue(thrown.getMessage().contains("clienteKO"), "La excepción debería indicar el fichero no localizado"); + + /* + * el fichero de la api cliente no existe + */ + thrown = assertThrows(FileNotFoundException.class, + + () -> underTest.getMetricInfo("downloads"), + "Debería haber lanzado la excepción de fichero no encontrado, pero no lo ha hecho"); + + assertTrue(thrown.getMessage().contains("clienteKO"), "La excepción debería indicar el fichero no localizado"); + } + +} diff --git a/src/test/java/us/muit/fs/a4i/test/config/MetricInfo.java b/src/test/java/us/muit/fs/a4i/test/config/MetricInfo.java deleted file mode 100644 index fbeacea4..00000000 --- a/src/test/java/us/muit/fs/a4i/test/config/MetricInfo.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * - */ -package us.muit.fs.a4i.test.config; - -import static org.junit.jupiter.api.Assertions.*; - -import java.io.IOException; -import java.util.HashMap; - -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import us.muit.fs.a4i.config.Checker; -import us.muit.fs.a4i.config.Context; - -/** - * @author isa - * - */ -class MetricInfo { - - /** - * Test method for {@link us.muit.fs.a4i.config.Context#getChecker()}. - */ - @Test - @Tag("Integracion") - void testGetChecker() { - try { - Checker checker=Context.getContext().getChecker(); - HashMap metricInfo=checker.getMetricInfo("issues"); - assertEquals(metricInfo.get("name"),"issues","No se ha leído bien el nombre de la métrica"); - assertEquals(metricInfo.get("type"),"java.lang.Integer","No se ha leído bien el tipo de la métrica"); - assertEquals(metricInfo.get("description"),"Tareas sin finalizar en el repositorio","No se ha leído bien la descripción de la métrica"); - assertEquals(metricInfo.get("unit"),"issues","No se ha leído bien las unidades de la métrica"); - } catch (IOException e) { - - e.printStackTrace(); - fail("No debería devolver esta excepción"); - } - - } - -} - diff --git a/src/test/java/us/muit/fs/a4i/test/config/package-info.java b/src/test/java/us/muit/fs/a4i/test/config/package-info.java index b0bbcda4..b80eef2a 100644 --- a/src/test/java/us/muit/fs/a4i/test/config/package-info.java +++ b/src/test/java/us/muit/fs/a4i/test/config/package-info.java @@ -1,7 +1,8 @@ /** - * Este paquete contiene los test para las clases de getión de configuración y contexto de la api Audit4Improve + * Este paquete contiene los test para las clases de gestión de configuración y + * contexto de la api Audit4Improve * - * @author Isabel Román + * @author Isabel Román * @version 0.0 */ package us.muit.fs.a4i.test.config; \ No newline at end of file diff --git a/src/test/java/us/muit/fs/a4i/test/control/IssuesRatioIndicatorTest.java b/src/test/java/us/muit/fs/a4i/test/control/IssuesRatioIndicatorTest.java new file mode 100644 index 00000000..07cf1ea8 --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/control/IssuesRatioIndicatorTest.java @@ -0,0 +1,119 @@ +package us.muit.fs.a4i.test.control; + +/*** + * @author celllarod, curso 22/23 + * Pruebas añadidas por alumnos del curso 22/23 para probar la clase IssuesRatioIndicator + */ + +import java.util.logging.Logger; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import static org.mockito.Mockito.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.mockito.Mockito; + +import us.muit.fs.a4i.control.strategies.IssuesRatioIndicatorStrategy; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.model.entities.ReportItemI; + +public class IssuesRatioIndicatorTest { + + private static Logger log = Logger.getLogger(IssuesRatioIndicatorTest.class.getName()); + + /** + * @throws java.lang.Exception + */ + @BeforeAll + static void setUpBeforeClass() throws Exception { + } + + /** + * @throws java.lang.Exception + */ + @AfterAll + static void tearDownAfterClass() throws Exception { + } + + /** + * @throws java.lang.Exception + */ + @BeforeEach + void setUp() throws Exception { + } + + /** + * @throws java.lang.Exception + */ + @AfterEach + void tearDown() throws Exception { + } + + @Test + public void testCalcIndicator() throws NotAvailableMetricException { + // Creamos los mocks necesarios + ReportItemI mockOpenIssues = Mockito.mock(ReportItemI.class); + ReportItemI mockClosedIssues = Mockito.mock(ReportItemI.class); + + // Configuramos los mocks para devolver valores predefinidos + Mockito.when(mockOpenIssues.getName()).thenReturn("openIssues"); + Mockito.when(mockOpenIssues.getValue()).thenReturn(10.0); + + Mockito.when(mockClosedIssues.getName()).thenReturn("closedIssues"); + Mockito.when(mockClosedIssues.getValue()).thenReturn(5.0); + + // Creamos una instancia de IssuesRatioIndicator + IssuesRatioIndicatorStrategy indicator = new IssuesRatioIndicatorStrategy(); + + // Ejecutamos el método que queremos probar con los mocks como argumentos + List> metrics = Arrays.asList(mockOpenIssues, mockClosedIssues); + ReportItemI result = indicator.calcIndicator(metrics); + + // Comprobamos que el resultado es el esperado + Assertions.assertEquals("issuesRatio", result.getName()); + Assertions.assertEquals(2.0, result.getValue()); + Assertions.assertDoesNotThrow(() -> indicator.calcIndicator(metrics)); + } + + @Test + public void testCalcIndicatorThrowsNotAvailableMetricException() { + // Creamos los mocks necesarios + ReportItemI mockOpenIssues = Mockito.mock(ReportItemI.class); + + // Configuramos los mocks para devolver valores predefinidos + Mockito.when(mockOpenIssues.getName()).thenReturn("openIssues"); + Mockito.when(mockOpenIssues.getValue()).thenReturn(10.0); + + // Creamos una instancia de IssuesRatioIndicator + IssuesRatioIndicatorStrategy indicator = new IssuesRatioIndicatorStrategy(); + + // Ejecutamos el método que queremos probar con una sola métrica + List> metrics = Arrays.asList(mockOpenIssues); + // Comprobamos que se lanza la excepción adecuada + NotAvailableMetricException exception = Assertions.assertThrows(NotAvailableMetricException.class, + () -> indicator.calcIndicator(metrics)); + + } + + @Test + public void testRequiredMetrics() { + // Creamos una instancia de IssuesRatioIndicator + IssuesRatioIndicatorStrategy indicatorStrategy = new IssuesRatioIndicatorStrategy(); + + // Ejecutamos el método que queremos probar + List requiredMetrics = indicatorStrategy.requiredMetrics(); + + // Comprobamos que el resultado es el esperado + List expectedMetrics = Arrays.asList("openIssues", "closedIssues"); + Assertions.assertEquals(expectedMetrics, requiredMetrics); + } +} diff --git a/src/test/java/us/muit/fs/a4i/test/control/calculators/RepositoryCalculatorTest.java b/src/test/java/us/muit/fs/a4i/test/control/calculators/RepositoryCalculatorTest.java new file mode 100644 index 00000000..ef885c1f --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/control/calculators/RepositoryCalculatorTest.java @@ -0,0 +1,211 @@ +package us.muit.fs.a4i.test.control.calculators; + +/*** + * @author celllarod, curso 22/23 + * Pruebas añadidas por alumnos del curso 22/23 para probar la clase RepositoryCalculator + * REF: https://javadoc.io/doc/org.mockito/mockito-core/4.3.1/org/mockito/Mockito.html + */ + +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import us.muit.fs.a4i.control.IndicatorStrategy; +import us.muit.fs.a4i.control.ReportManagerI; +import us.muit.fs.a4i.control.calculators.RepositoryCalculator; +import us.muit.fs.a4i.control.strategies.IssuesRatioIndicatorStrategy; + +import us.muit.fs.a4i.exceptions.IndicatorException; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.IndicatorI; +import us.muit.fs.a4i.model.entities.ReportI; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItem.ReportItemBuilder; + +import us.muit.fs.a4i.model.entities.ReportItemI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +public class RepositoryCalculatorTest { + + private static Logger log = Logger.getLogger(RepositoryCalculatorTest.class.getName()); + + /** + * Test para el metodo calcIndicator de RepositoryCalculator cuando las métricas + * que se pasan no son las necesarias RepositoryCalculator debe antes obtenerlas + * {@link us.muit.fs.a4i.control.RepositoryCalculator.calcIndicator(String, + * ReportManagerI)}. + * + * @throws NotAvailableMetricException + * @throws ReportItemException + */ + @Test + @Tag("unidad") + @DisplayName("Prueba calcIndicator de RepositoryCalculator metricas incorrectas") + void testCalIndicatorNotRequiredMetrics() throws NotAvailableMetricException, ReportItemException { + // Creamos mocks necesarios, de menor a mayor + // primero las métricas + + ReportItemI metrica1 = Mockito.mock(ReportItemI.class); + ReportItemI metrica2 = Mockito.mock(ReportItemI.class); + // Tengo que hacer que metrica1 y metrica2 devuelvan lo que devolvería el de + // verdad + Mockito.when(metrica1.getName()).thenReturn("issues"); + Mockito.when(metrica1.getValue()).thenReturn(2); + + Mockito.when(metrica2.getName()).thenReturn("closedIssues"); + Mockito.when(metrica2.getValue()).thenReturn(1.0); + List metricsMock = new ArrayList<>(); + metricsMock.add(metrica1); + metricsMock.add(metrica2); + + // Ahora la estrategia de cálculo + // Empezando por Las dependencias que va indicar la estrategia de cálculo + List required = new ArrayList(); + required.add("otradiferente"); + required.add("closedIssues"); + + // El indicador que va a calcular + ReportItemI indicator = Mockito.mock(ReportItemI.class); + Mockito.when(indicator.getName()).thenReturn("indicador"); + + IndicatorStrategy indicatorStrategyMock = Mockito.mock(IndicatorStrategy.class); + Mockito.when(indicatorStrategyMock.requiredMetrics()).thenReturn(required); + Mockito.when(indicatorStrategyMock.calcIndicator(metricsMock)).thenReturn(indicator); + + // Ahora gestor de informe + // Empezamos creando el informe + ReportI report = Mockito.mock(ReportI.class); + Mockito.when(report.getAllMetrics()).thenReturn(metricsMock); + Mockito.when(report.getMetricByName("issues")).thenReturn(metrica1); + Mockito.when(report.getMetricByName("closedIssues")).thenReturn(metrica2); + + ReportManagerI reportManagerMock = Mockito.mock(ReportManagerI.class); + Mockito.when(reportManagerMock.getMetric("issues")).thenReturn(metrica1); + Mockito.when(reportManagerMock.getMetric("closedIssues")).thenReturn(metrica2); + Mockito.when(reportManagerMock.getReport()).thenReturn(report); + + + // Creamos la clase a probar + RepositoryCalculator repositoryCalculator = new RepositoryCalculator(); + repositoryCalculator.setIndicator("indicator", indicatorStrategyMock); + try { + repositoryCalculator.calcIndicator("indicator", reportManagerMock); + + + } catch (IndicatorException e) { + fail("No debería lanzar la excepción"); + e.printStackTrace(); + } + + // Verificamos que se ha invocado el método para añadir la métrica que falta, pero las otras no + Mockito.verify(reportManagerMock, times(1)).addMetric("otradiferente"); + Mockito.verify(reportManagerMock, times(0)).addMetric("issues"); + Mockito.verify(reportManagerMock, times(0)).addMetric("clossedIssues"); + + + // Verificamos que se han comprobado las métricas, invocado el método de cálculo + // y que el indicador se ha añadido al informe + Mockito.verify(indicatorStrategyMock, times(1)).requiredMetrics(); + Mockito.verify(indicatorStrategyMock, times(1)).calcIndicator(metricsMock); + Mockito.verify(report, times(1)).addIndicator(indicator); + //Al menos una vez + Mockito.verify(reportManagerMock,Mockito.atLeastOnce()).getReport(); + + Mockito.verify(report,Mockito.atLeastOnce()).getAllMetrics(); + } + + /** + * Test para el metodo calcIndicator de RepositoryCalculator cuando las métricas + * disponibles son suficientes + * {@link us.muit.fs.a4i.control.RepositoryCalculator.calcIndicator(String, + * ReportManagerI)}. + * + * @throws NotAvailableMetricException + * @throws ReportItemException + */ + @Test + @Tag("unidad") + @DisplayName("Prueba calcIndicator de RepositoryCalculator metricas correctas") + void testCalIndicatorWithRequiredMetrics() throws NotAvailableMetricException, ReportItemException { + // Si no queremos depender de los objetos reales habría que eliminar las + // dependencias, pero en este caso se ha etiquetado como prueba + // de integración + + // Creamos la clase a probar + RepositoryCalculator repositoryCalculator = new RepositoryCalculator(); + + // Creamos mocks necesarios + + ReportItemI metrica1 = Mockito.mock(ReportItemI.class); + ReportItemI metrica2 = Mockito.mock(ReportItemI.class); + // Tengo que hacer que metrica1 y metrica2 devuelvan lo que devolvería el de + // verdad + Mockito.when(metrica1.getName()).thenReturn("issues"); + Mockito.when(metrica1.getValue()).thenReturn(2); + + Mockito.when(metrica2.getName()).thenReturn("closedIssues"); + Mockito.when(metrica2.getValue()).thenReturn(1.0); + List metricsMock = new ArrayList<>(); + metricsMock.add(metrica1); + metricsMock.add(metrica2); + List required = new ArrayList(); + required.add("issues"); + required.add("closedIssues"); + + ReportItemI indicator = Mockito.mock(ReportItemI.class); + Mockito.when(indicator.getName()).thenReturn("indicador"); + + ReportI report = Mockito.mock(ReportI.class); + Mockito.when(report.getAllMetrics()).thenReturn(metricsMock); + + ReportManagerI reportManagerMock = Mockito.mock(ReportManagerI.class); + Mockito.when(reportManagerMock.getMetric("issues")).thenReturn(metrica1); + Mockito.when(reportManagerMock.getMetric("closedIssues")).thenReturn(metrica2); + Mockito.when(reportManagerMock.getReport()).thenReturn(report); + + IndicatorStrategy indicatorStrategyMock = Mockito.mock(IndicatorStrategy.class); + Mockito.when(indicatorStrategyMock.calcIndicator(metricsMock)).thenReturn(indicator); + Mockito.when(indicatorStrategyMock.requiredMetrics()).thenReturn(required); + + repositoryCalculator.setIndicator("indicator", indicatorStrategyMock); + try { + repositoryCalculator.calcIndicator("indicator", reportManagerMock); + } catch (IndicatorException e) { + fail("No debería lanzar la excepción"); + e.printStackTrace(); + } + // Verificamos que NO se ha invocado el método para añadir ninguna de las + // métricas + Mockito.verify(reportManagerMock, times(0)).addMetric("issues"); + Mockito.verify(reportManagerMock, times(0)).addMetric("clossedIssues"); + + // Verificamos que se han comprobado las métricas, invocado el método de cálculo + // y que el indicador se ha añadido al informe + Mockito.verify(indicatorStrategyMock, times(1)).requiredMetrics(); + Mockito.verify(indicatorStrategyMock, times(1)).calcIndicator(metricsMock); + Mockito.verify(report, times(1)).addIndicator(indicator); + + //Al menos una vez + Mockito.verify(reportManagerMock,Mockito.atLeastOnce()).getReport(); + Mockito.verify(report,Mockito.atLeastOnce()).getAllMetrics(); + } + +} diff --git a/src/test/java/us/muit/fs/a4i/test/control/managers/ReportManagerTest.java b/src/test/java/us/muit/fs/a4i/test/control/managers/ReportManagerTest.java new file mode 100644 index 00000000..45046983 --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/control/managers/ReportManagerTest.java @@ -0,0 +1,51 @@ +/** + * + */ +package us.muit.fs.a4i.test.control.managers; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.logging.Logger; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + + +import us.muit.fs.a4i.control.ReportManagerI; +import us.muit.fs.a4i.control.managers.ReportManager; +import us.muit.fs.a4i.model.entities.ReportI; + +/** + * @author Isabel Román Martínez + * + */ +public class ReportManagerTest { + private static Logger log = Logger.getLogger(ReportManagerTest.class.getName()); + + /** + * Test method for + * {@link us.muit.fs.a4i.control.ReportManager#ReportManager(us.muit.fs.a4i.model.entities.ReportI.ReportType)}. + */ + @Test + void testReportManager() { + ReportManagerI manager = null; + ReportI report = null; + try { + manager = new ReportManager(ReportI.ReportType.REPOSITORY); + assertNotNull(manager, "No se ha creado el gestor"); + log.info("Creado el gestor"); + report = manager.newReport("MIT-FS/Audit4Improve-API", ReportI.ReportType.REPOSITORY); + log.info("Creado el informe"); + } catch (Exception e) { + fail("Se lanza alguna excepción al crear el gestor o el informe"); + e.printStackTrace(); + } + assertNotNull(report, "No se ha construido el informe"); + System.out.println(report); + } + +} diff --git a/src/test/java/us/muit/fs/a4i/test/control/package-info.java b/src/test/java/us/muit/fs/a4i/test/control/package-info.java index 36a077c3..39375beb 100644 --- a/src/test/java/us/muit/fs/a4i/test/control/package-info.java +++ b/src/test/java/us/muit/fs/a4i/test/control/package-info.java @@ -1,7 +1,8 @@ /** - * Este paquete contiene las clases para probar los controladores de la api Audit4Improve + * Este paquete contiene las clases para probar los controladores de la api + * Audit4Improve * - * @author Isabel Román + * @author Isabel Román * @version 0.0 */ package us.muit.fs.a4i.test.control; \ No newline at end of file diff --git a/src/test/java/us/muit/fs/a4i/test/control/prueba.java b/src/test/java/us/muit/fs/a4i/test/control/prueba.java index c09159ef..1b29d48f 100644 --- a/src/test/java/us/muit/fs/a4i/test/control/prueba.java +++ b/src/test/java/us/muit/fs/a4i/test/control/prueba.java @@ -3,20 +3,13 @@ */ package us.muit.fs.a4i.test.control; - - -import us.muit.fs.a4i.model.remote.RemoteEnquirer; -import us.muit.fs.a4i.model.remote.GitHubRepositoryEnquirer; - import java.io.IOException; import us.muit.fs.a4i.config.Context; import us.muit.fs.a4i.control.ReportManagerI; import us.muit.fs.a4i.exceptions.ReportNotDefinedException; - import us.muit.fs.a4i.model.entities.ReportI; -import us.muit.fs.a4i.persistence.ExcelReportManager; -import us.muit.fs.a4i.persistence.FileManager; +import us.muit.fs.a4i.model.remote.RemoteEnquirer; import us.muit.fs.a4i.persistence.PersistenceManager; /** @@ -27,41 +20,35 @@ public class prueba { /** * @param args - * @throws ReportNotDefinedException + * @throws ReportNotDefinedException */ public static void main(String[] args) throws ReportNotDefinedException { - - ReportManagerI manager; - ReportI report; - PersistenceManager persister; - RemoteEnquirer builder; - try { - System.out.println("Listado de propiedades disponibles "+Context.getContext().getPropertiesNames()); - - } catch (IOException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - /* - manager=new ReportManager(); - builder=new GitHubRepositoryEnquirer(); - - persister=new ExcelReportManager(); - manager.setRemoteBuilder(builder); - manager.setPersistenceManager(persister); - - report=manager.createReport("MIT-FS/Audit4Improve-API"); - - try { - FileManager file=(FileManager) persister; - file.setName("test"); - manager.save(); - - } catch (ReportNotDefinedException e) { - System.out.println("Cuidado tiene que establecer el informe"); - e.printStackTrace(); + + ReportManagerI manager; + ReportI report; + PersistenceManager persister; + RemoteEnquirer builder; + try { + System.out.println("Listado de propiedades disponibles " + Context.getContext().getPropertiesNames()); + + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + /* + * manager=new ReportManager(); builder=new GitHubRepositoryEnquirer(); + * + * persister=new ExcelReportManager(); manager.setRemoteBuilder(builder); + * manager.setPersistenceManager(persister); + * + * report=manager.createReport("MIT-FS/Audit4Improve-API"); + * + * try { FileManager file=(FileManager) persister; file.setName("test"); + * manager.save(); + * + * } catch (ReportNotDefinedException e) { + * System.out.println("Cuidado tiene que establecer el informe"); + * e.printStackTrace(); } + */ } - */ -} } - diff --git a/src/test/java/us/muit/fs/a4i/test/control/strategies/ConventionsCompliantStrategyTest.java b/src/test/java/us/muit/fs/a4i/test/control/strategies/ConventionsCompliantStrategyTest.java new file mode 100644 index 00000000..ef738582 --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/control/strategies/ConventionsCompliantStrategyTest.java @@ -0,0 +1,156 @@ +package us.muit.fs.a4i.test.control.strategies; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.*; + +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.junit.jupiter.api.Assertions; + +import us.muit.fs.a4i.control.strategies.ConventionsCompliantStrategy; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.model.entities.ReportItemI; + +public class ConventionsCompliantStrategyTest { + // MARK: - Attributes + // The indicator strategy to be tested + static ConventionsCompliantStrategy repositoryIndicatorStrategy; + // The expected calculation of the indicator + static Double calculation; + + // MARK: - Setup methods + /** + * @throws java.lang.Exception + */ + @SuppressWarnings("unchecked") + @BeforeAll + static void setUpBeforeClass() throws Exception { + // Create the indicator strategy, here we use the default weights + repositoryIndicatorStrategy = new ConventionsCompliantStrategy(); + + // Based on the provided metrics, the indicator should be calculated as follows: + calculation = (0.2 * 0.86) + (0.2 * 0) + (0.2 * 0.92) + (0.2 * 0) + (0.2 * 0.98); + } + + // MARK: - Test methods + /** + * The indicator is calculated based on the following metrics: + * Metric 1: Ratio of commits complying with summary naming conventions (conventional commits). Must be above 85%. + + * Metric 2: Ratio of commits that have both a summary and a description. Must be above 85%. + * Metric 3: Ratio of issues that have at least one label, compared to the total amount of issues. Must be above 85%. + * Metric 4: The ratio of all branches that follow the correct naming conventions. Must be a 100% compliance. + * Metric 5: Ratio of pull requests following correct naming and description conventions. Must be above 85%. + * + * Where RepoIndicator = w1 * C1 + w2 * C2 + w3 * C3 + w4 * C4 + w5 * C5, and: + * C_i = M_i if M_i >= 0.85 else 0 when i in {1, 2, 3, 5} + * C_4 = 1 if M_4 == 1 else 0 + * @throws NotAvailableMetricException + */ + @Test + public void testCalcIndicator() throws NotAvailableMetricException { + // Mock the required metrics + ReportItemI mockConventionalCommits = Mockito.mock(ReportItemI.class); + ReportItemI mockCommitsWithDescription = Mockito.mock(ReportItemI.class); + ReportItemI mockIssuesWithLabels = Mockito.mock(ReportItemI.class); + ReportItemI mockGitFlowBranches = Mockito.mock(ReportItemI.class); + ReportItemI mockConventionalPullRequests = Mockito.mock(ReportItemI.class); + + // Mock the metric values + when(mockConventionalCommits.getValue()).thenReturn(0.86); + when(mockCommitsWithDescription.getValue()).thenReturn(0.84); + when(mockIssuesWithLabels.getValue()).thenReturn(0.92); + when(mockGitFlowBranches.getValue()).thenReturn(0.85); + when(mockConventionalPullRequests.getValue()).thenReturn(0.98); + // Mock the metric names + when(mockConventionalCommits.getName()).thenReturn("conventionalCommits"); + when(mockCommitsWithDescription.getName()).thenReturn("commitsWithDescription"); + when(mockIssuesWithLabels.getName()).thenReturn("issuesWithLabels"); + when(mockGitFlowBranches.getName()).thenReturn("gitFlowBranches"); + when(mockConventionalPullRequests.getName()).thenReturn("conventionalPullRequests"); + // Set up the list of metrics + List> metrics = Arrays.asList( + mockConventionalCommits, + mockCommitsWithDescription, + mockIssuesWithLabels, + mockGitFlowBranches, + mockConventionalPullRequests + ); + + // Calculate the indicator output + assertDoesNotThrow(() -> { + repositoryIndicatorStrategy.calcIndicator(metrics); + }, "The calculation threw an exception, which it should not."); + + ReportItemI result = repositoryIndicatorStrategy.calcIndicator(metrics); + + assertNotNull(result, "The indicator result is null."); + + // Check the result + Assertions.assertEquals(ConventionsCompliantStrategy.ID, + result.getName(), + "The indicator name is not correct, expected: " + ConventionsCompliantStrategy.ID + ", but was: " + result.getName()); + + Assertions.assertEquals(calculation, result.getValue(), "The indicator value from the calculation is not correct."); + } + + /** + * Test the calculation of the indicator when the required metrics are not available + */ + @Test + public void testCalcIndicatorThrowsNotAvailableMetricException() { + // Mock the required metrics + ReportItemI mockConventionalCommits = Mockito.mock(ReportItemI.class); + ReportItemI mockCommitsWithDescription = Mockito.mock(ReportItemI.class); + ReportItemI mockIssuesWithLabels = Mockito.mock(ReportItemI.class); + ReportItemI mockConventionalPullRequests = Mockito.mock(ReportItemI.class); + + // Mock the metric values + when(mockConventionalCommits.getValue()).thenReturn(0.86); + when(mockCommitsWithDescription.getValue()).thenReturn(0.84); + when(mockIssuesWithLabels.getValue()).thenReturn(0.92); + when(mockConventionalPullRequests.getValue()).thenReturn(0.98); + // Mock the metric names + when(mockConventionalCommits.getName()).thenReturn("conventionalCommits"); + when(mockCommitsWithDescription.getName()).thenReturn("commitsWithDescription"); + when(mockIssuesWithLabels.getName()).thenReturn("issuesWithLabels"); + when(mockConventionalPullRequests.getName()).thenReturn("conventionalPullRequests"); + // Set up the list of metrics + List> metrics = Arrays.asList( + mockConventionalCommits, + mockCommitsWithDescription, + mockIssuesWithLabels, + mockConventionalPullRequests + ); + + // Check that the method throws the expected exception + Assertions.assertThrows(NotAvailableMetricException.class, () -> { + repositoryIndicatorStrategy.calcIndicator(metrics); + }); + } + + /** + * Test the required metrics for the indicator calculation + */ + @Test + public void testRequiredMetrics() { + List expectedRequirements = Arrays.asList("conventionalCommits", "commitsWithDescription", "issuesWithLabels", "gitFlowBranches", "conventionalPullRequests"); + + for (String requirement : expectedRequirements) { + Assertions.assertTrue(repositoryIndicatorStrategy.requiredMetrics().contains(requirement), + "The required metrics for RepositoryIndicatorStrategyTest did not contain the following expected metric: " + requirement); + } + } +} diff --git a/src/test/java/us/muit/fs/a4i/test/control/strategies/DeveloperPerformanceIndicatorTest.java b/src/test/java/us/muit/fs/a4i/test/control/strategies/DeveloperPerformanceIndicatorTest.java new file mode 100644 index 00000000..0c6acf92 --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/control/strategies/DeveloperPerformanceIndicatorTest.java @@ -0,0 +1,74 @@ +/** + * + */ +package us.muit.fs.a4i.test.control.strategies; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +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.control.strategies.DeveloperPerformanceStrategy; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.Indicator; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItemI; + +/** + * + */ +class DeveloperPerformanceIndicatorTest { + + private DeveloperPerformanceStrategy indicador; + + @BeforeEach + public void setUp() { + DeveloperPerformanceStrategy indicador = new DeveloperPerformanceStrategy(); + } + + @Test + public void testCalcIndicator() throws ReportItemException, NotAvailableMetricException { + // Crear métricas simuladas + ReportItemI createdIssuesMes = new ReportItem.ReportItemBuilder("createdIssuesMes", 100.0) + .build(); + ReportItemI closedIssuesMes = new ReportItem.ReportItemBuilder("closedIssuesMes", 80.0).build(); + ReportItemI assignedDevIssuesMes = new ReportItem.ReportItemBuilder("assingedDevIssuesMes", 8.0) + .build(); + ReportItemI closedDevIssuesMes = new ReportItem.ReportItemBuilder("meanClosedIssuesLastMonth", + 5.0).build(); + + // Calcular el indicador + List> metrics = Arrays.asList(createdIssuesMes, closedIssuesMes, assignedDevIssuesMes, + closedDevIssuesMes); + ReportItemI result = indicador.calcIndicator(metrics); + + // Verificar el resultado + Double expectedIndicator = (80.0 / 100.0) / (5.0 / 8.0); + assertEquals(expectedIndicator, result.getValue()); + } + + @Test + public void testCalcIndicatorWithMissingMetrics() throws ReportItemException, NotAvailableMetricException { + // Crear métricas simuladas incompletas + ReportItemI createdIssuesMes = new ReportItem.ReportItemBuilder("createdIssuesMes", 100.0) + .build(); + ReportItemI meanClosedIssuesLastMonth = new ReportItem.ReportItemBuilder( + "meanClosedIssuesLastMonth", 80.0).build(); + + // Calcular el indicador + List> metrics = Arrays.asList(createdIssuesMes, meanClosedIssuesLastMonth); + + // Verificar que se lanza una excepción + assertThrows(NotAvailableMetricException.class, () -> { + indicador.calcIndicator(metrics); + }); + } +} diff --git a/src/test/java/us/muit/fs/a4i/test/control/strategies/FixTimeStrategyTest.java b/src/test/java/us/muit/fs/a4i/test/control/strategies/FixTimeStrategyTest.java new file mode 100644 index 00000000..38652f72 --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/control/strategies/FixTimeStrategyTest.java @@ -0,0 +1,88 @@ +package us.muit.fs.a4i.test.control.strategies; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import static org.mockito.Mockito.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.mockito.Mockito; + +import us.muit.fs.a4i.control.strategies.FixTimeStrategy; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.model.entities.ReportItemI; + +class FixTimeStrategyTest { + + @Test + public void testCalcIndicator() throws NotAvailableMetricException { + // Creamos los mocks necesarios + ReportItemI mockClasificacion = Mockito.mock(ReportItemI.class); + ReportItemI mockCorreccion = Mockito.mock(ReportItemI.class); + ReportItemI mockNum_errores = Mockito.mock(ReportItemI.class); + + // Configuramos los mocks para devolver valores predefinidos + Mockito.when(mockClasificacion.getName()).thenReturn("clasificacion"); + Mockito.when(mockClasificacion.getValue()).thenReturn(10.0); + + Mockito.when(mockCorreccion.getName()).thenReturn("correccion"); + Mockito.when(mockCorreccion.getValue()).thenReturn(5.0); + + Mockito.when(mockNum_errores.getName()).thenReturn("num_errores"); + Mockito.when(mockNum_errores.getValue()).thenReturn(5.0); + + // Creamos una instancia de la estrategia + FixTimeStrategy strategy = new FixTimeStrategy(); + + // Ejecutamos el método que queremos probar con los mocks como argumentos + List> metrics = Arrays.asList(mockClasificacion, mockCorreccion, mockNum_errores); + ReportItemI result = strategy.calcIndicator(metrics); + + // Comprobamos que el resultado es el esperado + Assertions.assertEquals("fixTime", result.getName()); + Assertions.assertEquals(3.0, result.getValue()); + Assertions.assertDoesNotThrow(() -> strategy.calcIndicator(metrics)); + + } + + @Test + public void testCalcIndicatorThrowsNotAvailableMetricException() { + // Creamos los mocks necesarios + ReportItemI mockClasificacion = Mockito.mock(ReportItemI.class); + + // Configuramos los mocks para devolver valores predefinidos + Mockito.when(mockClasificacion.getName()).thenReturn("clasificacion"); + Mockito.when(mockClasificacion.getValue()).thenReturn(10.0); + + // Creamos una instancia de la estrategia + FixTimeStrategy strategy = new FixTimeStrategy(); + + // Ejecutamos el método que queremos probar con una sola métrica + List> metrics = Arrays.asList(mockClasificacion); + // Comprobamos que se lanza la excepción adecuada + NotAvailableMetricException exception = Assertions.assertThrows(NotAvailableMetricException.class, + () -> strategy.calcIndicator(metrics)); + + } + + @Test + public void testRequiredMetrics() { + + // Creamos una instancia de la estrategia + FixTimeStrategy strategy = new FixTimeStrategy(); + + // Ejecutamos el método que queremos probar + List requiredMetrics = strategy.requiredMetrics(); + + // Comprobamos que el resultado es el esperado + List expectedMetrics = Arrays.asList("clasificacion", "correccion", "num_errores"); + Assertions.assertEquals(expectedMetrics, requiredMetrics); + } +} \ No newline at end of file diff --git a/src/test/java/us/muit/fs/a4i/test/control/strategies/IEFStrategyTest.java b/src/test/java/us/muit/fs/a4i/test/control/strategies/IEFStrategyTest.java new file mode 100644 index 00000000..c7bc0b1f --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/control/strategies/IEFStrategyTest.java @@ -0,0 +1,149 @@ + +package us.muit.fs.a4i.test.control.strategies; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +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.control.strategies.IEFStrategy; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; +import us.muit.fs.a4i.model.entities.ReportItemI; +import us.muit.fs.a4i.model.entities.ReportItem; + +public class IEFStrategyTest { + private static Logger log = Logger.getLogger(IEFStrategyTest.class.getName()); + private IEFStrategy strat; + + @BeforeEach + void setUp() { + strat = new IEFStrategy(); + } + @Test + public void testIEF_Excelente() throws NotAvailableMetricException, ReportItemException { + // Creamos mocks para cada métrica + ReportItemI mC = mock(ReportItemI.class); + ReportItemI mW = mock(ReportItemI.class); + ReportItemI mT = mock(ReportItemI.class); + ReportItemI mP = mock(ReportItemI.class); + + when(mC.getName()).thenReturn("cycleTime"); + when(mC.getValue()).thenReturn(0.0); + when(mW.getName()).thenReturn("waitTime"); + when(mW.getValue()).thenReturn(0.0); + when(mT.getName()).thenReturn("throughput"); + when(mT.getValue()).thenReturn(50.0); + when(mP.getName()).thenReturn("WIP"); + when(mP.getValue()).thenReturn(0.0); + + List> metrics = List.of(mC, mW, mT, mP); + log.info("Métricas de entrada: " + metrics); + + ReportItemI result = strat.calcIndicator(metrics); + log.info("Resultado IEF: " + result); + + assertEquals("IEF", result.getName()); + assertEquals(1.0, result.getValue(), 1e-6); + assertNotNull(result.getIndicator()); + assertEquals(IndicatorState.OK, result.getIndicator().getState()); + } + + @Test + public void testIEF_Bueno() throws NotAvailableMetricException, ReportItemException { + ReportItemI mC = mock(ReportItemI.class); + ReportItemI mW = mock(ReportItemI.class); + ReportItemI mT = mock(ReportItemI.class); + ReportItemI mP = mock(ReportItemI.class); + + when(mC.getName()).thenReturn("cycleTime"); + when(mC.getValue()).thenReturn(40.0); // 0.25 + when(mW.getName()).thenReturn("waitTime"); + when(mW.getValue()).thenReturn(20.0); // 0.25 + when(mT.getName()).thenReturn("throughput"); + when(mT.getValue()).thenReturn(35.0); // 0.7 + when(mP.getName()).thenReturn("WIP"); + when(mP.getValue()).thenReturn(5.0); // 0.25 + + List> metrics = List.of(mC, mW, mT, mP); + ReportItemI result = strat.calcIndicator(metrics); + + assertEquals("IEF", result.getName()); + assertEquals(0.8125, result.getValue(), 1e-4); // cae en "Excelente" + assertNotNull(result.getIndicator()); + assertEquals(IndicatorState.OK, result.getIndicator().getState()); // este valor da "Excelente" + } + + @Test + public void testIEF_Aceptable() throws NotAvailableMetricException, ReportItemException { + // Creamos mocks para cada métrica + ReportItemI mC = mock(ReportItemI.class); + ReportItemI mW = mock(ReportItemI.class); + ReportItemI mT = mock(ReportItemI.class); + ReportItemI mP = mock(ReportItemI.class); + + when(mC.getName()).thenReturn("cycleTime"); + when(mC.getValue()).thenReturn(80.0); + when(mW.getName()).thenReturn("waitTime"); + when(mW.getValue()).thenReturn(40.0); + when(mT.getName()).thenReturn("throughput"); + when(mT.getValue()).thenReturn(25.0); + when(mP.getName()).thenReturn("WIP"); + when(mP.getValue()).thenReturn(10.0); + + List> metrics = List.of(mC, mW, mT, mP); + log.info("Métricas de entrada: " + metrics); + + ReportItemI result = strat.calcIndicator(metrics); + log.info("Resultado IEF: " + result); + + assertEquals("IEF", result.getName()); + assertEquals(0.5, result.getValue(), 1e-6); + assertNotNull(result.getIndicator()); + assertEquals(IndicatorState.WARNING, result.getIndicator().getState()); + } + + @Test + public void testIEF_Bajo() throws NotAvailableMetricException, ReportItemException { + // Creamos mocks para cada métrica + ReportItemI mC = mock(ReportItemI.class); + ReportItemI mW = mock(ReportItemI.class); + ReportItemI mT = mock(ReportItemI.class); + ReportItemI mP = mock(ReportItemI.class); + + when(mC.getName()).thenReturn("cycleTime"); + when(mC.getValue()).thenReturn(200.0); + when(mW.getName()).thenReturn("waitTime"); + when(mW.getValue()).thenReturn(100.0); + when(mT.getName()).thenReturn("throughput"); + when(mT.getValue()).thenReturn(0.0); + when(mP.getName()).thenReturn("WIP"); + when(mP.getValue()).thenReturn(30.0); + + List> metrics = List.of(mC, mW, mT, mP); + log.info("Métricas de entrada: " + metrics); + + ReportItemI result = strat.calcIndicator(metrics); + log.info("Resultado IEF: " + result); + + assertEquals("IEF", result.getName()); + assertEquals(0.0, result.getValue(), 1e-6); + assertNotNull(result.getIndicator()); + assertEquals(IndicatorState.CRITICAL, result.getIndicator().getState()); + } + + @Test + public void testIEF_FaltanMetricas() throws ReportItemException { + ReportItemI mC = new ReportItem.ReportItemBuilder<>("cycleTime", 10.0).build(); + List> metrics = List.of(mC); + + assertThrows(NotAvailableMetricException.class, () -> strat.calcIndicator(metrics)); + } +} + + diff --git a/src/test/java/us/muit/fs/a4i/test/control/strategies/PRPerfomanceStrategyTest.java b/src/test/java/us/muit/fs/a4i/test/control/strategies/PRPerfomanceStrategyTest.java new file mode 100644 index 00000000..c47e191c --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/control/strategies/PRPerfomanceStrategyTest.java @@ -0,0 +1,84 @@ +package us.muit.fs.a4i.test.control.strategies; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import us.muit.fs.a4i.control.strategies.PRPerformanceStrategy; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.model.entities.ReportItemI; + +class PRPerfomanceStrategyTest { + + public void testCalcIndicator() throws NotAvailableMetricException { + // Creamos los mocks necesarios + ReportItemI mockAcceptedLastYear = Mockito.mock(ReportItemI.class); + ReportItemI mockAcceptedLastMonth = Mockito.mock(ReportItemI.class); + ReportItemI mockRejectedLastYear = Mockito.mock(ReportItemI.class); + ReportItemI mockRejectedLastMonth = Mockito.mock(ReportItemI.class); + + // Configuramos los mocks para devolver valores predefinidos + Mockito.when(mockAcceptedLastYear.getName()).thenReturn("PRAcceptedLastYear"); + Mockito.when(mockAcceptedLastYear.getValue()).thenReturn((double) 100); + + Mockito.when(mockAcceptedLastMonth.getName()).thenReturn("PRAcceptedLastMonth"); + Mockito.when(mockAcceptedLastMonth.getValue()).thenReturn((double) 10); + + Mockito.when(mockRejectedLastYear.getName()).thenReturn("PRRejectedLastYear"); + Mockito.when(mockRejectedLastYear.getValue()).thenReturn((double) 20); + + Mockito.when(mockRejectedLastMonth.getName()).thenReturn("PRRejectedLastMonth"); + Mockito.when(mockRejectedLastMonth.getValue()).thenReturn((double) 2); + + // Creamos una instancia de PRPerformanceStrategy + PRPerformanceStrategy indicator = new PRPerformanceStrategy(); + + // Ejecutamos el método que queremos probar con los mocks como argumentos + List> metrics = Arrays.asList(mockAcceptedLastYear, mockAcceptedLastMonth, + mockRejectedLastYear, mockRejectedLastMonth); + ReportItemI result = indicator.calcIndicator(metrics); + + // Comprobamos que el resultado es el esperado + Assertions.assertEquals("PRPerformance", result.getName()); + Assertions.assertEquals((double) 100 / 20 - (double) 10 / 2, result.getValue()); + Assertions.assertDoesNotThrow(() -> indicator.calcIndicator(metrics)); + } + + @Test + public void testCalcIndicatorThrowsNotAvailableMetricException() { + // Creamos los mocks necesarios + ReportItemI mockAcceptedLastYear = Mockito.mock(ReportItemI.class); + + // Configuramos los mocks para devolver valores predefinidos + Mockito.when(mockAcceptedLastYear.getName()).thenReturn("PRAcceptedLastYear"); + Mockito.when(mockAcceptedLastYear.getValue()).thenReturn((double) 100); + + // Creamos una instancia de PullRequestsAcceptanceIndicatorStrategy + PRPerformanceStrategy indicator = new PRPerformanceStrategy(); + + // Ejecutamos el método que queremos probar con una sola métrica + List> metrics = Arrays.asList(mockAcceptedLastYear); + // Comprobamos que se lanza la excepción adecuada + NotAvailableMetricException exception = Assertions.assertThrows(NotAvailableMetricException.class, + () -> indicator.calcIndicator(metrics)); + } + + @Test + public void testRequiredMetrics() { + // Creamos una instancia de PRPerformanceStrategy + PRPerformanceStrategy indicatorStrategy = new PRPerformanceStrategy(); + + // Ejecutamos el método que queremos probar + List requiredMetrics = indicatorStrategy.requiredMetrics(); + + // Comprobamos que el resultado es el esperado + List expectedMetrics = Arrays.asList("PRAcceptedLastYear", "PRAcceptedLastMonth", "PRRejectedLastMonth", + "PRRejectedLastYear"); + Assertions.assertEquals(expectedMetrics, requiredMetrics); + } +} diff --git a/src/test/java/us/muit/fs/a4i/test/control/strategies/PullRequestIndicatorStrategyTest.java b/src/test/java/us/muit/fs/a4i/test/control/strategies/PullRequestIndicatorStrategyTest.java new file mode 100644 index 00000000..42599e96 --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/control/strategies/PullRequestIndicatorStrategyTest.java @@ -0,0 +1,104 @@ +package us.muit.fs.a4i.test.control.strategies; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; +import us.muit.fs.a4i.model.entities.ReportItemI; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; +import us.muit.fs.a4i.control.strategies.PullRequestIndicatorStrategy; + +/** + * Equipo cuatro curso 23/24 + */ +class PullRequestIndicatorStrategyTest { + + @BeforeAll + static void setUpBeforeClass() throws Exception { + } + + @AfterAll + static void tearDownAfterClass() throws Exception { + } + + @BeforeEach + void setUp() throws Exception { + } + + @AfterEach + void tearDown() throws Exception { + } + + @Test + public void testCalcIndicator() throws NotAvailableMetricException { + // Creamos los mocks + ReportItemI mockPullReqTotales = Mockito.mock(ReportItemI.class); + ReportItemI mockPullReqCompletados = Mockito.mock(ReportItemI.class); + + // Configuramos los mocks para que el porcentaje sea el 60% + Mockito.when(mockPullReqTotales.getName()).thenReturn("totalPullReq"); + Mockito.when(mockPullReqTotales.getValue()).thenReturn(100.0); + + Mockito.when(mockPullReqCompletados.getName()).thenReturn("closedPullReq"); + Mockito.when(mockPullReqCompletados.getValue()).thenReturn(60.0); + + // Creamos la calculadora del indicador + PullRequestIndicatorStrategy indicadorCalc = new PullRequestIndicatorStrategy(); + + // Calculamos el indicador + List> metricas = Arrays.asList(mockPullReqTotales, mockPullReqCompletados); + ReportItemI resultado = indicadorCalc.calcIndicator(metricas); + + // Comprobamos que el resultado es el esperado + Assertions.assertEquals("pullRequestCompletion", resultado.getName()); + Assertions.assertEquals(60.0, resultado.getValue()); + Assertions.assertDoesNotThrow(() -> indicadorCalc.calcIndicator(metricas)); + } + + @Test + public void testCalcIndicatorThrowsNotAvailableMetricException() { + // Creamos un mock + ReportItemI mockPullReqTotales = Mockito.mock(ReportItemI.class); + + // Configuramos el mock + Mockito.when(mockPullReqTotales.getName()).thenReturn("totalPullReq"); + Mockito.when(mockPullReqTotales.getValue()).thenReturn(100.0); + + // Creamos la calculadora del indicador + PullRequestIndicatorStrategy indicadorCalc = new PullRequestIndicatorStrategy(); + + // Probamos con una sola métrica + List> metricas = Arrays.asList(mockPullReqTotales); + // Comprobamos que se lanza la excepción + NotAvailableMetricException exception = Assertions.assertThrows(NotAvailableMetricException.class, + () -> indicadorCalc.calcIndicator(metricas)); + + } + + @Test + public void testRequiredMetrics() { + // Creamos la calculadora del indicador + PullRequestIndicatorStrategy indicadorCalc = new PullRequestIndicatorStrategy(); + + // Obtenemos las metricas del indicador + List metricas = indicadorCalc.requiredMetrics(); + + // Comprobamos que el resultado es el esperado + List metricasEsperadas = Arrays.asList("totalPullReq", "closedPullReq"); + Assertions.assertEquals(metricasEsperadas, metricas); + } + +} diff --git a/src/test/java/us/muit/fs/a4i/test/control/strategies/TeamsBalanceStrategyTest.java b/src/test/java/us/muit/fs/a4i/test/control/strategies/TeamsBalanceStrategyTest.java new file mode 100644 index 00000000..a3e7e3ad --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/control/strategies/TeamsBalanceStrategyTest.java @@ -0,0 +1,90 @@ +package us.muit.fs.a4i.test.control.strategies; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Logger; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.mockito.Mockito.*; +import org.mockito.Mockito; + +import us.muit.fs.a4i.control.strategies.TeamsBalanceStrategy; +import us.muit.fs.a4i.exceptions.NotAvailableMetricException; + +import us.muit.fs.a4i.model.entities.ReportItemI; + +public class TeamsBalanceStrategyTest { + private static Logger log = Logger.getLogger(TeamsBalanceStrategyTest.class.getName()); + + /** + * @throws java.lang.Exception + */ + @BeforeAll + static void setUpBeforeClass() throws Exception { + } + + /** + * @throws java.lang.Exception + */ + @AfterAll + static void tearDownAfterClass() throws Exception { + } + + /** + * @throws java.lang.Exception + */ + @BeforeEach + void setUp() throws Exception { + } + + /** + * @throws java.lang.Exception + */ + @AfterEach + void tearDown() throws Exception { + } + + + @Test + public void testTeamsBalance() throws NotAvailableMetricException { + // Creamos los mocks necesarios + ReportItemI mockTeamBalance = Mockito.mock(ReportItemI.class); + ReportItemI mockRepositories = Mockito.mock(ReportItemI.class); + + // Configuramos los mocks para devolver valores predefinidos + Mockito.when(mockTeamBalance.getName()).thenReturn("teamsBalance"); + Mockito.when(mockTeamBalance.getValue()).thenReturn(1.0); + + Mockito.when(mockRepositories.getName()).thenReturn("repositories"); + Mockito.when(mockRepositories.getValue()).thenReturn(5.0); + + // Creamos una instancia de IssuesRatioIndicator + TeamsBalanceStrategy indicator = new TeamsBalanceStrategy(); + + // Ejecutamos el método que queremos probar con los mocks como argumentos + List> metrics = Arrays.asList(mockTeamBalance, mockRepositories); + log.info("MockteamBalance: " + mockTeamBalance); + log.info("MockRepositories: " + mockRepositories); + log.info("Metrics: " + metrics); + ReportItemI result = indicator.calcIndicator(metrics); + + // Comprobamos que el resultado es el esperado + log.info("Result: " + result); + Assertions.assertEquals("teamsBalance", result.getName()); + Assertions.assertEquals(20.0, result.getValue()); + Assertions.assertDoesNotThrow(()->indicator.calcIndicator(metrics)); + } + + + + +} + diff --git a/src/test/java/us/muit/fs/a4i/test/model/entities/MetricBuilderTest.java b/src/test/java/us/muit/fs/a4i/test/model/entities/MetricBuilderTest.java deleted file mode 100644 index 5148aaf8..00000000 --- a/src/test/java/us/muit/fs/a4i/test/model/entities/MetricBuilderTest.java +++ /dev/null @@ -1,170 +0,0 @@ -/** - * - */ -package us.muit.fs.a4i.test.model.entities; - -import static org.junit.jupiter.api.Assertions.*; - -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.util.Date; -import java.util.logging.Logger; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import us.muit.fs.a4i.model.entities.Metric.MetricBuilder; - -import us.muit.fs.a4i.exceptions.MetricException; -import us.muit.fs.a4i.model.entities.Metric; - -/** - *

- * Test para probar el constructor de objetos de tipo Metric - *

- * - * @author Isabel Román - * - */ -class MetricBuilderTest { - private static Logger log = Logger.getLogger(MetricBuilderTest.class.getName()); - - /** - * @throws java.lang.Exception Se incluye por defecto al crear automáticamente los tests con eclipse - */ - @BeforeAll - static void setUpBeforeClass() throws Exception { - //Acciones a realizar antes de ejecutar los tests de esta clase - } - - /** - * @throws java.lang.Exception Se incluye por defecto al crear automáticamente los tests con eclipse - */ - @AfterAll - static void tearDownAfterClass() throws Exception { - //Acciones a realizar después de ejecutar todos los tests de esta clase - } - - /** - * @throws java.lang.Exception Se incluye por defecto al crear automáticamente los tests con eclipse - */ - @BeforeEach - void setUp() throws Exception { - //Acciones a realizar antes de cada uno de los tests de esta clase - } - - /** - * @throws java.lang.Exception Se incluye por defecto al crear automáticamente los tests con eclipse - */ - @AfterEach - void tearDown() throws Exception { - //Acciones a realizar después de cada uno de los tests de esta clase - } - - /** - * Test para el constructor Test de MetricBuilder: - * {@link us.muit.fs.a4i.model.entities.Metric.MetricBuilder#MetricBuilder(java.lang.String, java.lang.Object)}. - * @see org.junit.jupiter.api.Test - */ - @Test - @Tag("integración") - @DisplayName("Prueba constructor métricas, las clases Context y Checker ya están disponibles") - void testMetricBuilder() { - - //Comenzamos probando el caso más sencillo, la métrica existe y el tipo es correcto - MetricBuilder underTest = null; - try { - underTest = new MetricBuilder("watchers", 33); - } catch (MetricException e) { - fail("No debería haber saltado esta excepción"); - e.printStackTrace(); - } - Metric newMetric = underTest.build(); - log.info("Métrica creada "+newMetric.toString()); - assertEquals("watchers", newMetric.getName(), "El nombre establecido no es correcto"); - assertEquals(33, newMetric.getValue(), "El valor establecido no es correcto"); - assertEquals(Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)).toString(), - newMetric.getDate().toString(), "La fecha establecida no es correcta"); - assertEquals(newMetric.getDescription(), "Observadores, en la web aparece com forks","La descripción no coincide con la del fichero de configuración"); - assertNull(newMetric.getSource(), "El origen no debería estar incluido"); - assertEquals(newMetric.getUnit(),"watchers", "No debería incluir las unidades"); - - // A continuación se prueba que se hace verificación correcta del tipo de métrica - // Prueba un tipo que no se corresponde con el definido por la métrica, tiene que lanzar la excepción MetricException - try { - underTest = new MetricBuilder("watchers", "hola"); - fail("Debería haber lanzado una excepción"); - } catch (MetricException e) { - log.info("Lanza la excepción adecuada, MetricException"); - - } catch (Exception e) { - fail("La excepción capturada es " + e + " cuando se esperaba de tipo MetricException"); - } - //Forma ALTERNATIVA de verificar el lanzamiento de una excepción, usando la verificación assertThrows - MetricException thrown = assertThrows(MetricException.class, () -> { - new MetricBuilder("watchers", "hola"); - }, "Se esperaba la excepción MetricException"); - //verifica también que el mensaje es correcto - assertEquals("Métrica watchers no definida o tipo java.lang.String incorrecto", thrown.getMessage(),"El mensaje de la excepción no es correcto"); - //El constructor de métricas no permite que se incluyan métricas no definidas - // Prueba una métrica que no existe - try { - underTest = new MetricBuilder("pepe", "hola"); - fail("Debería haber lanzado una excepción"); - } catch (MetricException e) { - log.info("Lanza la excepción adecuada, MetricException"); - - } catch (Exception e) { - fail("La excepción capturada es " + e + " cuando se esperaba de tipo MetricException"); - } - - } - - /** - * Test method for - * {@link us.muit.fs.a4i.model.entities.Metric.MetricBuilder#description(java.lang.String)}. - */ - @Test - void testDescription() { - fail("Not yet implemented"); // TODO - } - - /** - * Test method for - * {@link us.muit.fs.a4i.model.entities.Metric.MetricBuilder#source(java.lang.String)}. - */ - @Test - @Tag("integración") - @DisplayName("Prueba establecer fuente en constructor, las clases Context y Checker ya están disponibles") - void testSource() { - //Verificamos que si se establece una fuente en el constructor la métrica creada especifica esa fuente - MetricBuilder underTest = null; - try { - underTest = new MetricBuilder("watchers", 33); - } catch (MetricException e) { - fail("No debería haber saltado esta excepción"); - e.printStackTrace(); - } - underTest.source("GitHub"); - Metric newMetric = underTest.build(); - log.info("Métrica creada "+newMetric.toString()); - assertEquals("GitHub",newMetric.getSource(),"Source no tiene el valor esperado"); - - } - - /** - * Test method for - * {@link us.muit.fs.a4i.model.entities.Metric.MetricBuilder#unit(java.lang.String)}. - */ - @Test - void testUnit() { - fail("Not yet implemented"); // TODO - } - - -} diff --git a/src/test/java/us/muit/fs/a4i/test/model/entities/ReportItemTest.java b/src/test/java/us/muit/fs/a4i/test/model/entities/ReportItemTest.java new file mode 100644 index 00000000..5a9a4bb3 --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/model/entities/ReportItemTest.java @@ -0,0 +1,183 @@ +/** + * + */ +package us.muit.fs.a4i.test.model.entities; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.logging.Logger; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItem.ReportItemBuilder; + +/** + *

+ * Test para probar el constructor de objetos de tipo ReportItem + *

+ * + * @author Isabel Rom�n + * + */ +class ReportItemTest { + private static Logger log = Logger.getLogger(ReportItemTest.class.getName()); + + /** + * @throws java.lang.Exception Se incluye por defecto al crear automáticamente + * los tests con eclipse + */ + @BeforeAll + static void setUpBeforeClass() throws Exception { + // Acciones a realizar antes de ejecutar los tests de esta clase + } + + /** + * @throws java.lang.Exception Se incluye por defecto al crear automáticamente + * los tests con eclipse + */ + @AfterAll + static void tearDownAfterClass() throws Exception { + // Acciones a realizar después de ejecutar todos los tests de esta clase + } + + /** + * @throws java.lang.Exception Se incluye por defecto al crear automáticamente + * los tests con eclipse + */ + @BeforeEach + void setUp() throws Exception { + // Acciones a realizar antes de cada uno de los tests de esta clase + } + + /** + * @throws java.lang.Exception Se incluye por defecto al crear automáticamente + * los tests con eclipse + */ + @AfterEach + void tearDown() throws Exception { + // Acciones a realizar después de cada uno de los tests de esta clase + } + + /** + * Test para el constructor Test de ReportItemBuilder: + * {@link us.muit.fs.a4i.model.entities.ReportItem.ReportItemBuilder#ReportItemBuilder(java.lang.String, java.lang.Object)}. + * + * @see org.junit.jupiter.api.Test + */ + @Test + @Tag("unidad") + @DisplayName("Prueba constructor reportItem, las clases Context y Checker ya est�n disponibles") + void testReportItemBuilder() { + + // Comenzamos probando el caso más sencillo, la métrica existe y el tipo es + // correcto + ReportItemBuilder underTest = null; + try { + underTest = new ReportItemBuilder("watchers", 33); + } catch (ReportItemException e) { + fail("Watchers existe y no deber�a haber saltado esta excepci�n"); + e.printStackTrace(); + } + ReportItem newMetric = underTest.build(); + log.info("M�trica creada " + newMetric.toString()); + assertEquals("watchers", newMetric.getName(), "El nombre establecido no es correcto"); + assertEquals(33, newMetric.getValue(), "El valor establecido no es correcto"); + assertEquals(Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)).toString(), + newMetric.getDate().toString(), "La fecha establecida no es correcta"); + assertEquals(newMetric.getDescription(), "Observadores, en la web aparece com forks", + "La descripci�n no coincide con la del fichero de configuraci�n"); + assertNull(newMetric.getSource(), "El origen no deber�a estar incluido"); + assertEquals(newMetric.getUnit(), "watchers", "No deber�a incluir las unidades"); + + // A continuación se prueba que se hace verificación correcta del tipo de + // métrica + // Prueba un tipo que no se corresponde con el definido por la métrica, tiene + // que lanzar la excepción MetricException + try { + underTest = new ReportItemBuilder("watchers", "hola"); + fail("Deber�a haber lanzado una excepción"); + } catch (ReportItemException e) { + log.info("Lanza la excepci�n adecuada, ReportItemException"); + + } catch (Exception e) { + fail("La excepci�n capturada es " + e + " cuando se esperaba de tipo ReportItemException"); + } + // Forma ALTERNATIVA de verificar el lanzamiento de una excepción, usando la + // verificación assertThrows + ReportItemException thrown = assertThrows(ReportItemException.class, () -> { + new ReportItemBuilder("watchers", "hola"); + }, "Se esperaba la excepci�n ReportItemException"); + // verifica también que el mensaje es correcto + assertEquals("ReportItem watchers no definido o tipo java.lang.String incorrecto", thrown.getMessage(), + "El mensaje de la excepci�n no es correcto"); + // El constructor de métricas no permite que se incluyan métricas no definidas + // Prueba una métrica que no existe + try { + underTest = new ReportItemBuilder("pepe", "hola"); + fail("Deber�a haber lanzado una excepci�n"); + } catch (ReportItemException e) { + log.info("Lanza la excepci�n adecuada, ReportItemException"); + + } catch (Exception e) { + fail("La excepci�n capturada es " + e + " cuando se esperaba de tipo ReportItemException"); + } + + } + + /** + * Test method for + * {@link us.muit.fs.a4i.model.entities.ReportItem.ReportItemBuilder#description(java.lang.String)}. + */ + @Test + void testDescription() { + fail("Not yet implemented"); // TODO + } + + /** + * Test method for + * {@link us.muit.fs.a4i.model.entities.ReportItem.ReportItemBuilder#source(java.lang.String)}. + */ + @Test + @Tag("integraci�n") + @DisplayName("Prueba establecer fuente en constructor, las clases Context y Checker ya est�n disponibles") + void testSource() { + // Verificamos que si se establece una fuente en el constructor la métrica + // creada especifica esa fuente + ReportItemBuilder underTest = null; + try { + underTest = new ReportItemBuilder("watchers", 33); + } catch (ReportItemException e) { + fail("El elemento watchers existe, no deber�a haber saltado esta excepción"); + e.printStackTrace(); + } + underTest.source("GitHub"); + ReportItem newMetric = underTest.build(); + log.info("M�trica creada: " + newMetric.toString()); + assertEquals("GitHub", newMetric.getSource(), "Source no tiene el valor esperado"); + + } + + /** + * Test method for + * {@link us.muit.fs.a4i.model.entities.ReportItem.ReportItemBuilder#unit(java.lang.String)}. + */ + @Test + void testUnit() { + fail("Not yet implemented"); // TODO + } + +} \ No newline at end of file diff --git a/src/test/java/us/muit/fs/a4i/test/model/entities/ReportTest.java b/src/test/java/us/muit/fs/a4i/test/model/entities/ReportTest.java index 3f9f7f4c..2bb2391c 100644 --- a/src/test/java/us/muit/fs/a4i/test/model/entities/ReportTest.java +++ b/src/test/java/us/muit/fs/a4i/test/model/entities/ReportTest.java @@ -1,85 +1,86 @@ package us.muit.fs.a4i.test.model.entities; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.atLeast; + import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.Date; import java.util.logging.Logger; - -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.mockitoSession; -import static org.mockito.Mockito.times; -import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; - +import org.mockito.junit.jupiter.MockitoExtension; import us.muit.fs.a4i.control.IndicatorsCalculator; -import us.muit.fs.a4i.exceptions.IndicatorException; import us.muit.fs.a4i.model.entities.Indicator; -import us.muit.fs.a4i.model.entities.Metric; import us.muit.fs.a4i.model.entities.Report; import us.muit.fs.a4i.model.entities.ReportI; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; - +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItemI; /** - *

Test para probar la clase Report

- * @author Isabel Román + *

+ * Test para probar la clase Report + *

+ * + * @author Isabel Román * */ @ExtendWith(MockitoExtension.class) class ReportTest { - private static Logger log=Logger.getLogger(ReportTest.class.getName()); + private static Logger log = Logger.getLogger(ReportTest.class.getName()); /** - *

Objetos tipo Mock, sustitutos de las clases de las que depende Report

+ *

+ * Objetos tipo Mock, sustitutos de las clases de las que depende Report + *

* */ - @Mock(serializable =true) - private static IndicatorsCalculator indCalcMock= Mockito.mock(IndicatorsCalculator.class); - - - //Servirán para conocer el argumento con el que se ha invocado algún método de alguno de los mocks (sustitutos o representantes) - //ArgumentCaptor es un genérico, indico al declararlo el tipo del argumento que quiero capturar + @Mock(serializable = true) + private static IndicatorsCalculator indCalcMock = Mockito.mock(IndicatorsCalculator.class); + + // Servirán para conocer el argumento con el que se ha invocado algún método + // de alguno de los mocks (sustitutos o representantes) + // ArgumentCaptor es un genérico, indico al declararlo el tipo del argumento + // que quiero capturar @Captor private ArgumentCaptor intCaptor; @Captor private ArgumentCaptor strCaptor; @Captor - private ArgumentCaptor metricCaptor; + private ArgumentCaptor reportItemCaptor; @Captor private ArgumentCaptor indicatorCaptor; @Captor private ArgumentCaptor reportCaptor; - + @Mock(serializable = true) - private static Metric metricIntMock= Mockito.mock(Metric.class); + private static ReportItem metricIntMock = Mockito.mock(ReportItem.class); @Mock(serializable = true) - private static Metric metricDatMock= Mockito.mock(Metric.class); + private static ReportItem metricDatMock = Mockito.mock(ReportItem.class); @Mock(serializable = true) - private static Indicator indicatorIntMock= Mockito.mock(Indicator.class); - - + private static ReportItem indicatorIntMock = Mockito.mock(ReportItem.class); + @Mock(serializable = true) + private static ReportItem indicatorDatMock = Mockito.mock(ReportItem.class); private static Report reportTested; - + /** * @throws java.lang.Exception */ @BeforeAll static void setUpBeforeClass() throws Exception { - + } /** @@ -94,8 +95,7 @@ static void tearDownAfterClass() throws Exception { */ @BeforeEach void setUp() throws Exception { - - + } /** @@ -106,209 +106,306 @@ void tearDown() throws Exception { } /** - * Test del constructor simple - * Test method for {@link us.muit.fs.a4i.model.entities.Report#Report()}. + * Test del constructor simple Test method for + * {@link us.muit.fs.a4i.model.entities.Report#Report()}. */ @Test @Tag("noacabado") void testReport() { - - fail("Not yet implemented"); // TODO + // fail("Not yet implemented"); // TODO + // El constructor está formado por el string id y por el tipo type. + reportTested = new Report("entityId"); + // reportTested=new Report("entityId"); falta el type + reportTested = new Report(); + ReportI.ReportType type = reportTested.getType(); + assertEquals(type, reportTested.getType(), "No se establece correctamente el tipo del informe"); + assertEquals("entityId", reportTested.getEntityId(), + "No se establece correctamente el identificador del informe"); } /** - * Test del constructor pasándole el id - * Test method for {@link us.muit.fs.a4i.model.entities.Report#Report(java.lang.String)}. + * Test del constructor pasándole el id Test method for + * {@link us.muit.fs.a4i.model.entities.Report#Report(java.lang.String)}. */ @Test @Tag("noacabado") void testReportString() { - reportTested=new Report("id"); - - assertEquals("id",reportTested.getId(),"No se establece correctamente el identificador del informe"); - - + reportTested = new Report("entityId"); + assertEquals("entityId", reportTested.getEntityId(), + "No se establece correctamente el identificador del informe"); + + } + + @Test + @Tag("noacabado") + void testReportType() { + reportTested = new Report(); + ReportI.ReportType type = reportTested.getType(); + assertEquals(type, reportTested.getType(), "No se establece correctamente el tipo del informe"); } /** - * Test method for {@link us.muit.fs.a4i.model.entities.Report#getMetricByName(java.lang.String)}. + * Test method for + * {@link us.muit.fs.a4i.model.entities.Report#getMetricByName(java.lang.String)}. */ @Test @Tag("noacabado") void testGetMetricByName() { - fail("Not yet implemented"); // TODO + // TODO + String name = "nombre"; + // Metric metric=new Metric(); + ReportItem metric = null; + reportTested = new Report(); + reportTested.getMetricByName(name); + assertEquals(null, reportTested.getMetricByName(name), + "No se establece correctamente el buscador del nombre de la métrica"); + } /** - * Test method for {@link us.muit.fs.a4i.model.entities.Report#addMetric(us.muit.fs.a4i.model.entities.Metric)}. + * Test method for + * {@link us.muit.fs.a4i.model.entities.Report#addMetric(us.muit.fs.a4i.model.entities.Metric)}. */ @Test void testAddMetric() { - reportTested=new Report(); - setMetricsMocks(); - //Primero se prueba a añadir una métrica de tipo Integer + reportTested = new Report(); + setMetricsMocks(); + // Primero se prueba a añadir una métrica de tipo Integer reportTested.addMetric(metricIntMock); - //Verifico que se ha consultado el nombre una vez al invocar este método, se usa como clave para meterlo en un mapa, hay que consultarlo - //¿Por qué falla? ¿Con qué no había contado? ¿Hay problemas en el test o en el código? - //Prueba a sustituir por la línea comentada - Mockito.verify(metricIntMock).getName(); - //Mockito.verify(metricIntMock, atLeast(1)).getName(); - Metric metric=reportTested.getMetricByName("issues"); - assertEquals(metric.getValue(),3,"Debería tener el valor especificado en el mock"); - assertEquals(metric.getDescription(),"Tareas sin finalizar en el repositorio","Debería tener el valor especificado en el mock"); - - //Ahora se prueba una métrica de tipo Date + /* + * //Verifico que se ha consultado el nombre una vez al invocar este m�todo, se + * usa como clave para meterlo en un mapa, hay que consultarlo //�Por qu� falla? + * �Con qu� no hab�a contado? �Hay problemas en el test o en el c�digo? //Prueba + * a sustituir por la l�nea comentada //Mockito.verify(metricIntMock).getName(); + * Mockito.verify(metricIntMock, atLeast(1)).getName(); ReportItem + * metric=reportTested.getMetricByName("issues"); + * assertEquals(metric.getValue(), + * 3,"Deber�a tener el valor especificado en el mock"); + * assertEquals(metric.getDescription(),"Tareas sin finalizar en el repositorio" + * ,"Deber�a tener el valor especificado en el mock"); + */ + + // Verifico que se ha consultado el nombre una vez al invocar este m�todo, se + // usa como clave para meterlo en un mapa, hay que consultarlo + // �Por qu� falla? �Con qu� no hab�a contado? �Hay problemas en el test o en el + // c�digo? + // Prueba a sustituir por la l�nea comentada + Mockito.verify(metricIntMock, atLeast(1)).getName(); + // Mockito.verify(metricIntMock, atLeast(1)).getName(); + ReportItemI metric = reportTested.getMetricByName("issues"); + assertEquals(metric.getValue(), 3, "Deber�aa tener el valor especificado en el mock"); + assertEquals(metric.getDescription(), "Tareas sin finalizar en el repositorio", + "Deber�a tener el valor especificado en el mock"); + + // Ahora se prueba una métrica de tipo Date reportTested.addMetric(metricDatMock); - metric=reportTested.getMetricByName("lastPush"); - assertEquals(metric.getValue(),metricDatMock.getValue(),"Debería tener el valor especificado en el mock"); - assertEquals(metric.getDescription(),"Último push realizado en el repositorio","Debería tener el valor especificado en el mock"); - - //Ahora se prueba a añadir otra vez la misma métrica pero con otro valor + metric = reportTested.getMetricByName("lastPush"); + assertEquals(metric.getValue(), metricDatMock.getValue(), "Debería tener el valor especificado en el mock"); + assertEquals(metric.getDescription(), "Último push realizado en el repositorio", + "Debería tener el valor especificado en el mock"); + + // Ahora se prueba a añadir otra vez la misma métrica pero con otro valor reportTested.addMetric(metricIntMock); - Mockito.when(metricIntMock.getValue()).thenReturn(55); - metric=reportTested.getMetricByName("issues"); - assertEquals(metric.getValue(),55,"Debería tener el valor especificado en el mock"); - assertEquals(metric.getDescription(),"Tareas sin finalizar en el repositorio","Debería tener el valor especificado en el mock"); + Mockito.when(metricIntMock.getValue()).thenReturn(55); + metric = reportTested.getMetricByName("issues"); + assertEquals(metric.getValue(), 55, "Debería tener el valor especificado en el mock"); + assertEquals(metric.getDescription(), "Tareas sin finalizar en el repositorio", + "Debería tener el valor especificado en el mock"); } /** - * Test method for {@link us.muit.fs.a4i.model.entities.Report#getIndicatorByName(java.lang.String)}. + * Test method for + * {@link us.muit.fs.a4i.model.entities.Report#getIndicatorByName(java.lang.String)}. */ @Test @Tag("noacabado") void testGetIndicatorByName() { - fail("Not yet implemented"); // TODO + // TODO + String name = "nombre"; + // Metric metric=new Metric(); + ReportItem indicator = null; + reportTested = new Report(); + // reportTested.getIndicatorByName(name); + assertEquals(null, reportTested.getIndicatorByName(name), + "No se establece correctamente el buscador del nombre del indicador"); } /** - * Test method for {@link us.muit.fs.a4i.model.entities.Report#addIndicator(us.muit.fs.a4i.model.entities.Indicator)}. + * Test method for + * {@link us.muit.fs.a4i.model.entities.Report#addIndicator(us.muit.fs.a4i.model.entities.Indicator)}. */ @Test @Tag("noacabado") void testAddIndicator() { - fail("Not yet implemented"); // TODO + // fail("Not yet implemented"); // TODO, HECHO + + reportTested = new Report(); + setIndicatorsMocks(); + // Primero se prueba a añadir una métrica de tipo Integer + reportTested.addIndicator(indicatorIntMock); + // Verifico que se ha consultado el nombre una vez al invocar este método, se + // usa como clave para meterlo en un mapa, hay que consultarlo + // ¿Por qué falla? ¿Con qué no había contado? ¿Hay problemas en el test o + // en el código? + // Prueba a sustituir por la línea comentada + Mockito.verify(indicatorIntMock).getName(); + // Mockito.verify(metricIntMock, atLeast(1)).getName(); + ReportItemI indicator = reportTested.getIndicatorByName("issues"); + assertEquals(indicator.getValue(), 3, "Debería tener el valor especificado en el mock"); + assertEquals(indicator.getDescription(), "Tareas sin finalizar en el repositorio", + "Debería tener el valor especificado en el mock"); + + // Ahora se prueba una métrica de tipo Date + reportTested.addIndicator(indicatorDatMock); + indicator = reportTested.getIndicatorByName("lastPush"); + assertEquals(indicator.getValue(), metricDatMock.getValue(), "Debería tener el valor especificado en el mock"); + assertEquals(indicator.getDescription(), "Último push realizado en el repositorio", + "Debería tener el valor especificado en el mock"); + + // Ahora se prueba a añadir otra vez la misma métrica pero con otro valor + reportTested.addIndicator(indicatorIntMock); + Mockito.when(indicatorIntMock.getValue()).thenReturn(55); + indicator = reportTested.getIndicatorByName("issues"); + assertEquals(indicator.getValue(), 55, "Debería tener el valor especificado en el mock"); + assertEquals(indicator.getDescription(), "Tareas sin finalizar en el repositorio", + "Debería tener el valor especificado en el mock"); + } /** - * Test method for {@link us.muit.fs.a4i.model.entities.Report#calcIndicator(java.lang.String)}. + * Test method for + * {@link us.muit.fs.a4i.model.entities.Report#calcIndicator(java.lang.String)}. */ - @Test - void testCalcIndicator() { - //Definimos calculadora de tipo repositorio - Mockito.when(indCalcMock.getReportType()).thenReturn(Report.Type.REPOSITORY); - - - //Se configura la calculadora de indicadores del informe - try { - reportTested.setIndicatorsCalculator(indCalcMock); - Mockito.verify(indCalcMock).getReportType(); - } catch (IndicatorException e1) { - fail("No debería lanzar la excepción"); - } - //Se solicita el cálculo de un indicador determinado - reportTested.calcIndicator("issues"); - //Se observa con qué parámetros se invoca a la calculadora de indicadores - try { - Mockito.verify(indCalcMock).calcIndicator(strCaptor.capture(), reportCaptor.capture()); - //Elimine el comentario que aparece a continuación, ejecute el test y explique por qué falla - //Mockito.verify(indCalcMock).calcAllIndicators(reportTested); - } catch (IndicatorException e) { - fail("No debería lanzar la excepción"); - } - - //Se verfica que se usa el nombre correcto y se pasa la referencia al informe correcto - assertEquals("issues",strCaptor.getValue(),"Se solicita el cálculo de la métrica adecuada"); - assertEquals(reportTested,reportCaptor.getValue(),"Se pasa la referencia correcta del informe"); - //Hago un test que asegure que el propio informe captura y gestiona la excepción de que el indicador no existe - try { - Mockito.doThrow(new IndicatorException("El indicador no existe")).when(indCalcMock).calcIndicator("indKO", reportTested); - reportTested.calcIndicator("indKO"); - - } catch (IndicatorException e) { - fail("La excepción la debe gestionar el propio informe"); - } - - } +// @Test +// void testCalcIndicator() { +// //Definimos calculadora de tipo repositorio +// Mockito.when(indCalcMock.getReportType()).thenReturn(Report.Type.REPOSITORY); +// +// +// //Se configura la calculadora de indicadores del informe +// try { +// reportTested.setIndicatorsCalculator(indCalcMock); +// Mockito.verify(indCalcMock).getReportType(); +// } catch (IndicatorException e1) { +// fail("No debería lanzar la excepción"); +// } +// //Se solicita el cálculo de un indicador determinado +// reportTested.calcIndicator("issues"); +// //Se observa con qué parámetros se invoca a la calculadora de indicadores +// try { +// Mockito.verify(indCalcMock).calcIndicator(strCaptor.capture(), reportCaptor.capture()); +// //Elimine el comentario que aparece a continuación, ejecute el test y explique por qué falla +// //Mockito.verify(indCalcMock).calcAllIndicators(reportTested); +// } catch (IndicatorException e) { +// fail("No debería lanzar la excepción"); +// } +// +// //Se verfica que se usa el nombre correcto y se pasa la referencia al informe correcto +// assertEquals("issues",strCaptor.getValue(),"Se solicita el cálculo de la métrica adecuada"); +// assertEquals(reportTested,reportCaptor.getValue(),"Se pasa la referencia correcta del informe"); +// //Hago un test que asegure que el propio informe captura y gestiona la excepción de que el indicador no existe +// try { +// Mockito.doThrow(new IndicatorException("El indicador no existe")).when(indCalcMock).calcIndicator("indKO", reportTested); +// reportTested.calcIndicator("indKO"); +// +// } catch (IndicatorException e) { +// fail("La excepción la debe gestionar el propio informe"); +// } +// +// } /** - * Test method for {@link us.muit.fs.a4i.model.entities.Report#setId(java.lang.String)}. + * Test method for + * {@link us.muit.fs.a4i.model.entities.Report#setId(java.lang.String)}. */ - @Test - @Tag("noacabado") - void testSetId() { - fail("Not yet implemented"); // TODO - } +// @Test +// @Tag("noacabado") +// void testSetId() { +// fail("Not yet implemented"); // TODO +// } /** * Test method for {@link us.muit.fs.a4i.model.entities.Report#getId()}. */ @Test @Tag("noacabado") - void testGetId() { + void testGetEntityId() { fail("Not yet implemented"); // TODO } /** - * Test method for {@link us.muit.fs.a4i.model.entities.Report#setIndicatorsCalculator(us.muit.fs.a4i.control.IndicatorsCalculator)}. + * Test method for + * {@link us.muit.fs.a4i.model.entities.Report#setIndicatorsCalculator(us.muit.fs.a4i.control.IndicatorsCalculator)}. */ - @Test - void testSetIndicatorsCalculator() { - //Definimos calculadora de tipo repositorio - Mockito.when(indCalcMock.getReportType()).thenReturn(ReportI.Type.REPOSITORY); - ReportI orgReport=new Report(ReportI.Type.ORGANIZATION); - ReportI repoReport=new Report(ReportI.Type.REPOSITORY); - ReportI report=new Report(); - //Vamos a probar establecer la calculadora en un informe que no tiene el tipo aún establecido (Debería tener el tipo de la calculadora al final) - //Para ello usamos report - try { - report.setIndicatorsCalculator(indCalcMock); - //Se ha tenido que consultar el tipo de calculadora - Mockito.verify(indCalcMock).getReportType(); - assertEquals(indCalcMock.getReportType(),report.getType()); - } catch (IndicatorException e) { - fail("No debería lanzar excepción"); - } - - //Vamos a probar a establecer la calculadora si el tipo de ambos coincide, uso repoReport - try { - repoReport.setIndicatorsCalculator(indCalcMock); - //Se ha tenido que consultar el tipo de calculadora - //Mockito.verify(indCalcMock, times(2)).getReportType(); - assertEquals(indCalcMock.getReportType(),repoReport.getType()); - } catch (IndicatorException e) { - fail("No debería lanzar excepción"); - } - - //Vamos a probar a establecer la calculadora si el tipo de la calculadora discrepa con el tipo del informe, uso orgReport - - try { - orgReport.setIndicatorsCalculator(indCalcMock); - //Se ha tenido que consultar el tipo de calculadora - fail("Debe saltar una excepción antes, no debería llegar aquí"); - } catch (IndicatorException e) { - - log.info("Ha saltado la excepción de indicador"); - //Suponga que los requisitos cambian, le piden que el mensaje debe ser "El tipo de la calculadora discrepa del tipo del informe" - //Cambie el test para que lo verifique y ejecute ¿Qué ocurre? - assertEquals(e.getMessage(),"La calculadora no concuerda con el tipo de informe","El mensaje es correcto"); - } catch(Exception e) { - fail("La excepción no es del tipo IndicatorException como se esperaba"); - } - //Esta verificación es para mostrar que se puede analizar también el comportamiento interno de la clase - //en esta ocasión el número de veces que invoca a la calculadora durante el test - //Probar a cambiar 5 por otro número y ejecutar - Mockito.verify(indCalcMock,times(5)).getReportType(); - - } +// @Test +// void testSetIndicatorsCalculator() { +// //Definimos calculadora de tipo repositorio +// Mockito.when(indCalcMock.getReportType()).thenReturn(ReportI.Type.REPOSITORY); +// ReportI orgReport=new Report(ReportI.Type.ORGANIZATION); +// ReportI repoReport=new Report(ReportI.Type.REPOSITORY); +// ReportI report=new Report(); +// //Vamos a probar establecer la calculadora en un informe que no tiene el tipo aún establecido (Debería tener el tipo de la calculadora al final) +// //Para ello usamos report +// try { +// report.setIndicatorsCalculator(indCalcMock); +// //Se ha tenido que consultar el tipo de calculadora +// Mockito.verify(indCalcMock).getReportType(); +// assertEquals(indCalcMock.getReportType(),report.getType()); +// } catch (IndicatorException e) { +// fail("No debería lanzar excepción"); +// } +// +// //Vamos a probar a establecer la calculadora si el tipo de ambos coincide, uso repoReport +// try { +// repoReport.setIndicatorsCalculator(indCalcMock); +// //Se ha tenido que consultar el tipo de calculadora +// //Mockito.verify(indCalcMock, times(2)).getReportType(); +// assertEquals(indCalcMock.getReportType(),repoReport.getType()); +// } catch (IndicatorException e) { +// fail("No debería lanzar excepción"); +// } +// +// //Vamos a probar a establecer la calculadora si el tipo de la calculadora discrepa con el tipo del informe, uso orgReport +// +// try { +// orgReport.setIndicatorsCalculator(indCalcMock); +// //Se ha tenido que consultar el tipo de calculadora +// fail("Debe saltar una excepción antes, no debería llegar aquí"); +// } catch (IndicatorException e) { +// +// log.info("Ha saltado la excepción de indicador"); +// //Suponga que los requisitos cambian, le piden que el mensaje debe ser "El tipo de la calculadora discrepa del tipo del informe" +// //Cambie el test para que lo verifique y ejecute ¿Qué ocurre? +// assertEquals(e.getMessage(),"La calculadora no concuerda con el tipo de informe","El mensaje es correcto"); +// } catch(Exception e) { +// fail("La excepción no es del tipo IndicatorException como se esperaba"); +// } +// //Esta verificación es para mostrar que se puede analizar también el comportamiento interno de la clase +// //en esta ocasión el número de veces que invoca a la calculadora durante el test +// //Probar a cambiar 5 por otro número y ejecutar +// Mockito.verify(indCalcMock,times(5)).getReportType(); +// +// } + // ---------------------------------------------------------------------------------------------------------- /** * Test method for {@link us.muit.fs.a4i.model.entities.Report#toString()}. + * toString devuelve un tipo string de repoinfo formado por: "Informaci�n del + * Informe:\n - M�tricas: " "\n Clave: " + clave + metrics.get(clave) "\n Clave: + * " + clave + indicators.get(clave) */ @Test @Tag("noacabado") void testToString() { - fail("Not yet implemented"); // TODO + reportTested = new Report(); + String refString = "referencia"; + try { + assertEquals(reportTested.toString().getClass(), refString.getClass(), "Comparación de tipo String"); + } catch (Exception e) { + fail("ToString no devuelve un tipo String"); + } } + // ---------------------------------------------------------------------------------------------------------- /** * Test method for {@link us.muit.fs.a4i.model.entities.Report#getAllMetrics()}. @@ -316,17 +413,53 @@ void testToString() { @Test @Tag("noacabado") void testGetAllMetrics() { - fail("Not yet implemented"); // TODO + ReportItemI metric = null; + reportTested = new Report(); + + try { + assertNull(reportTested.getAllMetrics(), "All metric Null"); + } catch (Exception e) { + fail("All metric null"); + } } + void setMetricsMocks() { - Date date=Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)); + Date date = Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)); Mockito.when(metricIntMock.getName()).thenReturn("issues"); - Mockito.when(metricIntMock.getDescription()).thenReturn("Tareas sin finalizar en el repositorio"); - Mockito.when(metricIntMock.getValue()).thenReturn(3); - + Mockito.when(metricIntMock.getDescription()).thenReturn("Tareas sin finalizar en el repositorio"); + Mockito.when(metricIntMock.getValue()).thenReturn(3); + Mockito.when(metricDatMock.getName()).thenReturn("lastPush"); - Mockito.when(metricDatMock.getDescription()).thenReturn("Último push realizado en el repositorio"); + Mockito.when(metricDatMock.getDescription()).thenReturn("Último push realizado en el repositorio"); Mockito.when(metricDatMock.getValue()).thenReturn(date); } + /** + * Test method for + * {@link us.muit.fs.a4i.model.entities.Report#getAllIndicators()}. + */ + @Test + @Tag("noacabado") + void testGetAllIndicators() { + ReportItemI indicator = null; + reportTested = new Report(); + + try { + assertNull(reportTested.getAllIndicators(), "All indicator Null"); + } catch (Exception e) { + fail("Al indicators null"); + } + } + + void setIndicatorsMocks() { + Date date = Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)); + Mockito.when(indicatorIntMock.getName()).thenReturn("issues"); + Mockito.when(indicatorIntMock.getDescription()).thenReturn("Tareas sin finalizar en el repositorio"); + Mockito.when(indicatorIntMock.getValue()).thenReturn(3); + + Mockito.when(indicatorDatMock.getName()).thenReturn("lastPush"); + Mockito.when(indicatorDatMock.getDescription()).thenReturn("Último push realizado en el repositorio"); + Mockito.when(indicatorDatMock.getValue()).thenReturn(date); + } + } diff --git a/src/test/java/us/muit/fs/a4i/test/model/entities/package-info.java b/src/test/java/us/muit/fs/a4i/test/model/entities/package-info.java index 8d68599e..35d38a83 100644 --- a/src/test/java/us/muit/fs/a4i/test/model/entities/package-info.java +++ b/src/test/java/us/muit/fs/a4i/test/model/entities/package-info.java @@ -1,7 +1,8 @@ /** - * Este paquete contiene las clases para probar las entidades de la api Audit4Improve + * Este paquete contiene las clases para probar las entidades de la api + * Audit4Improve * - * @author Isabel Román + * @author Isabel Román * @version 0.0 */ package us.muit.fs.a4i.test.model.entities; \ No newline at end of file diff --git a/src/test/java/us/muit/fs/a4i/test/model/entities/testFont.java b/src/test/java/us/muit/fs/a4i/test/model/entities/testFont.java new file mode 100644 index 00000000..91a6dfed --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/model/entities/testFont.java @@ -0,0 +1,58 @@ +/** + * + */ +package us.muit.fs.a4i.test.model.entities; + +import static org.junit.jupiter.api.Assertions.*; + +import java.awt.Color; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import us.muit.fs.a4i.model.entities.Font; + +/** + * + */ +class testFont { + + /** + * @throws java.lang.Exception + */ + @BeforeAll + static void setUpBeforeClass() throws Exception { + } + + /** + * Test method for {@link us.muit.fs.a4i.model.entities.Font#Font(java.lang.String, int, java.lang.String)}. + */ + @Test + void testFontStringIntString() { + fail("Not yet implemented"); + } + + /** + * Test method for {@link us.muit.fs.a4i.model.entities.Font#getColor()}. + */ + @Test + void testConstructor() { + Font underTest=new Font(java.awt.Font.SANS_SERIF,java.awt.Font.BOLD,12,"azul"); + assertEquals(Color.BLUE.toString(),underTest.getColor().toString(),"No se ha establecido bien el color"); + assertEquals(java.awt.Font.SANS_SERIF,underTest.getFont().getFamily(),"No se ha puesto bien el tipo de fuente"); + assertEquals(java.awt.Font.BOLD, underTest.getFont().getStyle(),"El estilo no se ha puesto bien"); + underTest=new Font("Verdana",12,"azul"); + assertEquals(Color.BLUE.toString(),underTest.getColor().toString(),"No se ha establecido bien el color"); + assertEquals("Verdana",underTest.getFont().getFamily(),"No se ha puesto bien el tipo de fuente"); + assertEquals(java.awt.Font.PLAIN, underTest.getFont().getStyle(),"El estilo no se ha puesto bien"); + underTest=new Font("azul"); + assertEquals(Color.BLUE.toString(),underTest.getColor().toString(),"No se ha establecido bien el color"); + assertEquals(java.awt.Font.SERIF,underTest.getFont().getFamily(),"No se ha puesto bien el tipo de fuente"); + assertEquals(java.awt.Font.PLAIN, underTest.getFont().getStyle(),"El estilo no se ha puesto bien"); + underTest=new Font(); + assertEquals(Color.BLACK.toString(),underTest.getColor().toString(),"No se ha establecido bien el color"); + assertEquals(java.awt.Font.SERIF,underTest.getFont().getFamily(),"No se ha puesto bien el tipo de fuente"); + assertEquals(java.awt.Font.PLAIN, underTest.getFont().getStyle(),"El estilo no se ha puesto bien"); + } + +} diff --git a/src/test/java/us/muit/fs/a4i/test/model/remote/GitHubDeveloperEnquirerTest.java b/src/test/java/us/muit/fs/a4i/test/model/remote/GitHubDeveloperEnquirerTest.java new file mode 100644 index 00000000..b0584e59 --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/model/remote/GitHubDeveloperEnquirerTest.java @@ -0,0 +1,32 @@ +package us.muit.fs.a4i.test.model.remote; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.logging.Logger; + +import org.junit.jupiter.api.Test; + +import us.muit.fs.a4i.exceptions.MetricException; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.remote.GitHubDeveloperEnquirer; + +class GitHubDeveloperEnquirerTest { + private static Logger log = Logger.getLogger(GitHubOrganizationEnquirerTest.class.getName()); + GitHubDeveloperEnquirer ghEnquirer = new GitHubDeveloperEnquirer(); + + /** + * Test method for GitHubOrganizationEnquirer + * + * @throws MetricException + * @throws ReportItemException + */ + @Test + void testAssignedIssuesLastMonth() throws MetricException { + ReportItem metric = ghEnquirer.getMetric("closedIssuesLastMonth", "Isabel-Roman"); + assertEquals(metric.getName(), "closedIssuesLastMonth"); + log.info(metric.getValue().toString()); + log.info(metric.getDescription()); + } + +} diff --git a/src/test/java/us/muit/fs/a4i/test/model/remote/GitHubOrganizationEnquirerTest.java b/src/test/java/us/muit/fs/a4i/test/model/remote/GitHubOrganizationEnquirerTest.java new file mode 100644 index 00000000..c4529a31 --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/model/remote/GitHubOrganizationEnquirerTest.java @@ -0,0 +1,169 @@ +package us.muit.fs.a4i.test.model.remote; + +/** + * + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Logger; + +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHRepositoryStatistics; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.GHRepositoryStatistics.CodeFrequency; +import org.kohsuke.github.GHProject; + +import org.junit.jupiter.api.Test; +import org.kohsuke.github.GHOrganization; + +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; +import us.muit.fs.a4i.model.entities.ReportItemI; +import us.muit.fs.a4i.model.entities.ReportItem.ReportItemBuilder; +import us.muit.fs.a4i.model.remote.GitHubEnquirer; +import us.muit.fs.a4i.model.remote.GitHubOrganizationEnquirer; + +/** + * @author Roberto Lama (Curso 22/23) RECUERDA: el token tiene que tener permiso + * de acceso a la organización para que los tests puedan ejecutarse + * + */ +public class GitHubOrganizationEnquirerTest { + private static Logger log = Logger.getLogger(GitHubOrganizationEnquirerTest.class.getName()); + GitHubOrganizationEnquirer ghEnquirer = new GitHubOrganizationEnquirer(); + + /** + * Test method for GitHubOrganizationEnquirer + * + * @throws MetricException + * @throws ReportItemException + */ + @Test + void testGetPullRequest() throws MetricException, ReportItemException { + + // TEST 2: PullRequest + ReportItem metricsPullRequest = ghEnquirer.getMetric("pullRequests", "MIT-FS"); + + assertTrue(metricsPullRequest.getValue().intValue() > 0, "el número de pullRequests no es el esperado"); // Tiene + // PR + } + + /** + * Test method for GitHubOrganizationEnquirer + * + * @throws MetricException + * @throws ReportItemException + */ + @Test + void testGetRepositories() throws MetricException, ReportItemException { + + // TEST 3: Repositories + ReportItem metricsRepositories = ghEnquirer.getMetric("repositories", "MIT-FS"); + + assertTrue(metricsRepositories.getValue().intValue() > 0, "El número de repositorios no es el esperado"); // Tiene + // repositorios + } + + /** + * Test method for GitHubOrganizationEnquirer + * + * @throws MetricException + * @throws ReportItemException + */ + @Test + void testGetMembers() throws MetricException, ReportItemException { + + // TEST 4: Members + ReportItem metricsMembers = ghEnquirer.getMetric("members", "MIT-FS"); + + assertTrue(metricsMembers.getValue().intValue() > 0, "El número de miembros no es el esperado"); // Tiene 30 + // miembros + + } + + /** + * Test method for GitHubOrganizationEnquirer + * + * @throws MetricException + * @throws ReportItemException + */ + @Test + void testGetTeams() throws MetricException, ReportItemException { + + // TEST 5: Teams + ReportItem metricsTeams = ghEnquirer.getMetric("teams", "MIT-FS"); + assertTrue(metricsTeams.getValue().intValue() > 3, "El número equipos no es el esperado"); // Tiene más de 3 + + } + + /** + * Test method for GitHubOrganizationEnquirer + * + * @throws MetricException + * @throws ReportItemException + */ + @Test + void testGetOpenProjects() throws MetricException, ReportItemException { + + // TEST 6: OpenProjects + ReportItem op = ghEnquirer.getMetric("openProjects", "MIT-FS"); + assertTrue(op.getValue().intValue() > 0, "El número de proyectos abiertos no es el esperado"); + } + + /** + * Test method for GitHubOrganizationEnquirer + * + * @throws MetricException + * @throws ReportItemException + */ + @Test + void testGetClosedProjects() throws MetricException, ReportItemException { + + // TEST 7: ClosedProjects + ReportItem metricsClosedProjects = ghEnquirer.getMetric("closedProjects", "MIT-FS"); + + assertTrue(metricsClosedProjects.getValue().intValue() > 0, + "El número de proyectos cerrados no es el esperado"); + } + + /** + * Test method for GitHubOrganizationEnquirer + * + * @throws MetricException + * @throws ReportItemException + */ + @Test + void testGetRepositoriesWithOpenPullRequest() throws MetricException, ReportItemException { + + // TEST 1: RepositoriesWithOpenPullRequest + ReportItem metricsRepositoriesWithOpenPullRequest = ghEnquirer + .getMetric("repositoriesWithOpenPullRequest", "MIT-FS"); + + assertTrue(metricsRepositoriesWithOpenPullRequest.getValue().intValue() > 0, + "El número de repositorios con pull requests abiertos no es el esperado"); + } + /** + * Test method for + * GitHubOrganizationEnquirer + * @throws MetricException + * @throws ReportItemException + */ + @Test + void testTeamsBalance() throws MetricException, ReportItemException { + //"message":"Must have admin rights to Repository." + //Es necesario tener permisos de administrador en la cuenta dueña de la organización + ReportItem teamsBalance = ghEnquirer.getMetric("teamsBalance", "MIT-FS"); + assertNotNull(teamsBalance.getValue(), "No se ha podido obtener el balance equipos-repositorios"); + } + +} diff --git a/src/test/java/us/muit/fs/a4i/test/model/remote/GitHubRepositoryEnquirerTest.java b/src/test/java/us/muit/fs/a4i/test/model/remote/GitHubRepositoryEnquirerTest.java new file mode 100644 index 00000000..c7969ae8 --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/model/remote/GitHubRepositoryEnquirerTest.java @@ -0,0 +1,223 @@ +/** + * + */ +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.DisplayName; +import org.junit.jupiter.api.Test; + +import us.muit.fs.a4i.exceptions.MetricException; +import us.muit.fs.a4i.exceptions.ReportItemException; +import us.muit.fs.a4i.model.entities.ReportI; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItemI; +import us.muit.fs.a4i.model.remote.GitHubRepositoryEnquirer; + +/** + * + */ +class GitHubRepositoryEnquirerTest { + + private static Logger log = Logger.getLogger(GitHubOrganizationEnquirerTest.class.getName()); + GitHubRepositoryEnquirer ghEnquirer = new GitHubRepositoryEnquirer(); + + /** + * Test method for GitHubRepositoryEnquirer, verifing that issuesLastMonth is + * correctly obtained + * + * @throws MetricException + * @throws ReportItemException + */ + @Test + void testIssuesLastMonth() throws MetricException, ReportItemException { + ReportItem metric = ghEnquirer.getMetric("issuesLastMonth", "MIT-FS/Audit4Improve-API"); + assertEquals(metric.getName(), "issuesLastMonth"); + log.info(metric.getValue().toString()); + log.info(metric.getDescription()); + } + + /** + * Test method for GitHubRepositoryEnquirer, verifing that closedIssuesLastMonth + * is correctly obtained + * + * @throws MetricException + * @throws ReportItemException + */ + @Test + void testClosedIssuesLastMonth() throws MetricException, ReportItemException { + ReportItem metric = ghEnquirer.getMetric("closedIssuesLastMonth", "MIT-FS/Audit4Improve-API"); + assertEquals(metric.getName(), "closedIssuesLastMonth"); + log.info(metric.getValue().toString()); + log.info(metric.getDescription()); + } + + /** + * Test method for GitHubRepositoryEnquirer, verifing that closedIssuesLastMonth + * is correctly obtained + * + * @throws MetricException + * @throws ReportItemException + */ + @Test + void testMeanClosedIssuesLastMonth() throws MetricException, ReportItemException { + ReportItem metric = ghEnquirer.getMetric("meanClosedIssuesLastMonth", "MIT-FS/Audit4Improve-API"); + assertEquals(metric.getName(), "meanClosedIssuesLastMonth"); + log.info(metric.getValue().toString()); + log.info(metric.getDescription()); + } + + /** + * Test method for + * {@link us.muit.fs.a4i.model.remote.GitHubEnquirer#getAvailableMetrics()}. + */ + @Test + void testGetAvailableMetrics() { + List availableMetrics = ghEnquirer.getAvailableMetrics(); + log.info(availableMetrics.toString()); + } + + @Test + void testGetTotalPullRequests() throws MetricException { + + // Nombre de la métrica que queremos consultar + String nombreMetrica = "totalPullReq"; + + // Repositorio del que se quiere obtener la métrica + String repositoryId = "MIT-FS/Audit4Improve-API"; + + // Variable para almacenar el número total de pull requests + ReportItem metrica = null; + + // Creamos el RemoteEnquirer para el repositorio GitHub + GitHubRepositoryEnquirer enquirer = new GitHubRepositoryEnquirer(); + + // Obtenemos el número total de pull requests + metrica = enquirer.getMetric(nombreMetrica, repositoryId); + log.info(metrica.toString()); + + // Comprobamos que el resultado coincida con el número total de pull requests + // real + assertTrue(metrica.getValue() > 0, "Bad number of PR"); + + } + + /** + * @throws MetricException + */ + @Test + void testCompletedPullRequests() throws MetricException { + + // Nombre de la métrica que queremos consultar + String nombreMetrica = "closedPullReq"; + + // Repositorio del que se quiere obtener la métrica + String repositoryId = "MIT-FS/Audit4Improve-API"; + + // Variable para almacenar el número de pull requests completados + ReportItem metrica = null; + + // Creamos el RemoteEnquirer para el repositorio GitHub + GitHubRepositoryEnquirer enquirer = new GitHubRepositoryEnquirer(); + + // Obtenemos el número de pull requests completados + metrica = enquirer.getMetric(nombreMetrica, repositoryId); + log.info(metrica.toString()); + + // Comprobamos que el resultado coincida con el número de pull requests + // completados real + + assertTrue(0 < metrica.getValue(), "Bad number of closed PR"); + } + + // Test del equipo 1 del curso 23/24 + // MARK: Tests + @Test + @DisplayName("Test getConventionalCommits") + @SuppressWarnings("rawtypes") + void testGetConventionalCommits() { + ReportItemI reportItem = testGetMetric("conventionalCommits"); + assertNotNull(reportItem, "Getting conventional commits failed: reportItem is null"); + assertNotNull(reportItem.getValue(), "Getting conventional commits failed: value is null"); + // Check that the value is a number between 0 and 1 + double value = (double) reportItem.getValue(); + assertTrue(value >= 0 && value <= 1, "Getting conventional commits failed: value is not between 0 and 1"); + } + + @Test + @DisplayName("Test issuesWithLabels") + @SuppressWarnings("rawtypes") + void testGetIssuesWithLabels() { + ReportItemI reportItem = testGetMetric("issuesWithLabels"); + assertNotNull(reportItem, "Getting issues with labels failed: reportItem is null"); + assertNotNull(reportItem.getValue(), "Getting issues with labels failed: value is null"); + // Check that the value is a number between 0 and 1 + double value = (double) reportItem.getValue(); + assertTrue(value >= 0 && value <= 1, "Getting issues with labels failed: value is not between 0 and 1"); + } + + @Test + @DisplayName("Test gitFlowBranches") + @SuppressWarnings("rawtypes") + void testGetGitFlowBranches() { + ReportItemI reportItem = testGetMetric("gitFlowBranches"); + assertNotNull(reportItem, "Getting git flow branches failed: reportItem is null"); + assertNotNull(reportItem.getValue(), "Getting git flow branches failed: value is null"); + // Check that the value is a number between 0 and 1 + double value = (double) reportItem.getValue(); + assertTrue(value >= 0 && value <= 1, "Getting git flow branches failed: value is not between 0 and 1"); + } + + @Test + @DisplayName("Test commitsWithDescription") + @SuppressWarnings("rawtypes") + void testGetCommits() { + ReportItemI reportItem = testGetMetric("commitsWithDescription"); + assertNotNull(reportItem, "Getting commits with description failed: reportItem is null"); + assertNotNull(reportItem.getValue(), "Getting commits with description failed: value is null"); + // Check that the value is a number between 0 and 1 + double value = (double) reportItem.getValue(); + assertTrue(value >= 0 && value <= 1, "Getting commits with description failed: value is not between 0 and 1"); + } + + @Test + @DisplayName("Test conventionalPullRequests") + @SuppressWarnings("rawtypes") + void testGetRepository() { + ReportItemI reportItem = testGetMetric("commitsWithDescription"); + assertNotNull(reportItem, "Getting repository failed: reportItem is null"); + assertNotNull(reportItem.getValue(), "Getting repository failed: value is null"); + // Check that the value is a number between 0 and 1 + double value = (double) reportItem.getValue(); + assertTrue(value >= 0 && value <= 1, "Getting repository failed: value is not between 0 and 1"); + } + + // MARK: Test helper methods + @SuppressWarnings("rawtypes") + ReportItemI testGetMetric(String metricString) { + log.info("Consultando metrica " + metricString); + ReportItemI reportItem; + try { + reportItem = ghEnquirer.getMetric(metricString, "MIT-FS/Audit4Improve-API"); + } catch (MetricException e) { + e.printStackTrace(); + fail("Exception thrown while getting conventional commits: " + e.getMessage()); + return null; + } + + assertNotNull(reportItem, "Getting the metric (" + metricString + ") failed: reportItem is null"); + return reportItem; + } + + //Test de construcción del informe (ReportI) + @Test + void testGetReport() { + ReportI report=ghEnquirer.buildReport("MIT-FS/Audit4Improve-API"); + assertNotNull(report,"No construye el informe"); + log.info("Informe construido "+report.toString()); + } +} diff --git a/src/test/java/us/muit/fs/a4i/test/model/remote/IEFRemoteEnquirerTest.java b/src/test/java/us/muit/fs/a4i/test/model/remote/IEFRemoteEnquirerTest.java new file mode 100644 index 00000000..54272aca --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/model/remote/IEFRemoteEnquirerTest.java @@ -0,0 +1,109 @@ +package us.muit.fs.a4i.test.model.remote; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.kohsuke.github.GitHub; + +import us.muit.fs.a4i.exceptions.MetricException; +import us.muit.fs.a4i.model.entities.ReportI; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItemI; +import us.muit.fs.a4i.model.remote.IEFRemoteEnquirer; + +/** + * Test de integración para IEFRemoteEnquirer, + * consulta las métricas Kanban–Scrum directamente en GitHub. + */ +class IEFRemoteEnquirerTest { + private static final Logger log = Logger.getLogger(IEFRemoteEnquirerTest.class.getName()); + + private static final String REPO = "MIT-FS/Audit4Improve-API-G10"; + + private GitHub github; + private IEFRemoteEnquirer ghEnquirer; + + @BeforeEach + void setUp() throws IOException { + // Asume que tienes la variable de entorno GITHUB_TOKEN con el token de acceso + String token = System.getenv("GITHUB_TOKEN"); + github = GitHub.connectUsingOAuth(token); + ghEnquirer = new IEFRemoteEnquirer(github); + } + + @Test + @DisplayName("cycleTime: media horas desde backlog hasta cierre") + void testCycleTime() throws MetricException { + ReportItem metric = (ReportItem) ghEnquirer.getMetric("cycleTime", REPO); + assertEquals("cycleTime", metric.getName(), "El nombre debe ser cycleTime"); + double value = metric.getValue(); + log.info("cycleTime = " + value + "h"); + // Debe ser al menos 0 + assertTrue(value >= 0, "cycleTime debe ser >= 0"); + assertNotNull(metric.getDescription(), "Debe tener descripción"); + } + + @Test + @DisplayName("waitTime: media horas hasta entrar en progreso") + void testWaitTime() throws MetricException { + ReportItem metric = (ReportItem) ghEnquirer.getMetric("waitTime", REPO); + assertEquals("waitTime", metric.getName(), "El nombre debe ser waitTime"); + double value = metric.getValue(); + log.info("waitTime = " + value + "h"); + assertTrue(value >= 0, "waitTime debe ser >= 0"); + assertNotNull(metric.getDescription(), "Debe tener descripción"); + } + + @Test + @DisplayName("throughput: tareas cerradas última semana") + void testThroughput() throws MetricException { + ReportItem metric = (ReportItem) ghEnquirer.getMetric("throughput", REPO); + assertEquals("throughput", metric.getName(), "El nombre debe ser throughput"); + double value = metric.getValue(); + log.info("throughput = " + value + " tareas/semana"); + assertTrue(value >= 0, "throughput debe ser >= 0"); + assertNotNull(metric.getDescription(), "Debe tener descripción"); + } + + @Test + @DisplayName("WIP: promedio tareas en curso en cuatro etapas") + void testWIP() throws MetricException { + ReportItem metric = (ReportItem) ghEnquirer.getMetric("WIP", REPO); + assertEquals("WIP", metric.getName(), "El nombre debe ser WIP"); + double value = metric.getValue(); + log.info("WIP = " + value + " tareas"); + assertTrue(value >= 0, "WIP debe ser >= 0"); + assertNotNull(metric.getDescription(), "Debe tener descripción"); + } + + @Test + @DisplayName("getAvailableMetrics() debe listar las cuatro métricas") + void testGetAvailableMetrics() { + List list = ghEnquirer.getAvailableMetrics(); + log.info("Available metrics: " + list); + assertEquals(4, list.size(), "Deben ser cuatro métricas"); + assertTrue(list.containsAll(List.of("cycleTime","waitTime","throughput","WIP"))); + } + + @Test + @DisplayName("buildReport() debe devolver ReportI con 4 ítems") + void testBuildReport() { + ReportI report = ghEnquirer.buildReport(REPO); + assertNotNull(report, "El reporte no debe ser nulo"); + List items = new ArrayList<>(report.getAllMetrics()); + log.info("Informe generado con ítems: " + items); + assertEquals(4, items.size(), "Informe debe contener 4 métricas"); + // Verificamos rápidamente los nombres + assertTrue(items.stream().anyMatch(i -> "cycleTime".equals(i.getName()))); + assertTrue(items.stream().anyMatch(i -> "waitTime".equals(i.getName()))); + assertTrue(items.stream().anyMatch(i -> "throughput".equals(i.getName()))); + assertTrue(items.stream().anyMatch(i -> "WIP".equals(i.getName()))); + } +} diff --git a/src/test/java/us/muit/fs/a4i/test/model/remote/package-info.java b/src/test/java/us/muit/fs/a4i/test/model/remote/package-info.java index fa30a0f5..55fee05c 100644 --- a/src/test/java/us/muit/fs/a4i/test/model/remote/package-info.java +++ b/src/test/java/us/muit/fs/a4i/test/model/remote/package-info.java @@ -1,7 +1,8 @@ /** - * Este paquete contiene las clases para probar los objetos de acceso a datos de la api Audit4Improve + * Este paquete contiene las clases para probar los objetos de acceso a datos de + * la api Audit4Improve * - * @author Isabel Román + * @author Isabel Román * @version 0.0 */ package us.muit.fs.a4i.test.model.remote; \ No newline at end of file diff --git a/src/test/java/us/muit/fs/a4i/test/persistence/ExcelReportManagerTest.java b/src/test/java/us/muit/fs/a4i/test/persistence/ExcelReportManagerTest.java new file mode 100644 index 00000000..660b2c7e --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/persistence/ExcelReportManagerTest.java @@ -0,0 +1,190 @@ +/** + *

+ * Test para probar la clase ExcelReportManager + *

+ * + * @author Ivan Matas + * + */ +package us.muit.fs.a4i.test.persistence; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.logging.Logger; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import us.muit.fs.a4i.exceptions.ReportNotDefinedException; +import us.muit.fs.a4i.model.entities.Indicator; +import us.muit.fs.a4i.model.entities.IndicatorI.IndicatorState; +import us.muit.fs.a4i.model.entities.ReportI; +import us.muit.fs.a4i.model.entities.ReportItem; +import us.muit.fs.a4i.model.entities.ReportItemI; +import us.muit.fs.a4i.persistence.ExcelReportManager; +import us.muit.fs.a4i.persistence.ReportFormater; +import us.muit.fs.a4i.persistence.ReportFormaterI; + +@ExtendWith(MockitoExtension.class) + +class ExcelReportManagerTest { + private static Logger log = Logger.getLogger(ExcelReportManager.class.getName()); + + @Captor + private ArgumentCaptor intCaptor; + @Captor + private ArgumentCaptor strCaptor; + @Captor + private ArgumentCaptor FormaterCaptor; + + @Mock(serializable = true) + private static ReportItem metricIntMock = Mockito.mock(ReportItem.class); + @Mock(serializable = true) + private static ReportItem metricStrMock = Mockito.mock(ReportItem.class); + + @Mock(serializable = true) + private static ReportI informe = Mockito.mock(ReportI.class); + + @Mock(serializable = true) + private static ReportItem itemIndicatorIntMock = Mockito.mock(ReportItem.class); + + @Mock(serializable = true) + private static Indicator indicatorIntMock = Mockito.mock(Indicator.class); + + @Mock(serializable = true) + private static ReportItem itemIndicatorIntMock2 = Mockito.mock(ReportItem.class); + + @Mock(serializable = true) + private static Indicator indicatorIntMock2 = Mockito.mock(Indicator.class); + + @Mock(serializable = true) + private static ReportItem itemIndicatorIntMock3 = Mockito.mock(ReportItem.class); + + @Mock(serializable = true) + private static Indicator indicatorIntMock3 = Mockito.mock(Indicator.class); + + private static String excelPath; + private static String excelName; + private static ExcelReportManager underTest; + + /** + *

+ * Acciones a realizar antes de ejecutar los tests definidos en esta clase + *

+ * + * @throws java.lang.Exception + * @see org.junit.jupiter.api.BeforeAll + */ + @BeforeAll + static void setUpBeforeClass() throws Exception { + + } + + @Test + void ExcelCreation() { + List listaMetric = new ArrayList(); + List listaInd = new ArrayList(); + Date fecha = new Date(); + Mockito.when(metricIntMock.getValue()).thenReturn(55); + Mockito.when(metricIntMock.getName()).thenReturn("downloads"); + Mockito.when(metricIntMock.getUnit()).thenReturn("downloads"); + Mockito.when(metricIntMock.getDescription()).thenReturn("Descargas realizadas"); + Mockito.when(metricIntMock.getDate()).thenReturn(fecha); + listaMetric.add(metricIntMock); + + Mockito.when(metricStrMock.getValue()).thenReturn("2-2-22"); + Mockito.when(metricStrMock.getName()).thenReturn("lastPush"); + Mockito.when(metricStrMock.getUnit()).thenReturn("date"); + Mockito.when(metricStrMock.getDescription()).thenReturn("Último push realizado en el repositorio"); + Mockito.when(metricStrMock.getDate()).thenReturn(fecha); + listaMetric.add(metricStrMock); + + Mockito.when(itemIndicatorIntMock.getValue()).thenReturn(22); + Mockito.when(itemIndicatorIntMock.getName()).thenReturn("otracosa"); + Mockito.when(itemIndicatorIntMock.getUnit()).thenReturn("cosas"); + Mockito.when(itemIndicatorIntMock.getDescription()).thenReturn("Indicador Ejemplo"); + Mockito.when(itemIndicatorIntMock.getDate()).thenReturn(fecha); + Mockito.when(indicatorIntMock.getState()).thenReturn(IndicatorState.CRITICAL); + Mockito.when(itemIndicatorIntMock.getIndicator()).thenReturn(indicatorIntMock); + listaInd.add(itemIndicatorIntMock); + + Mockito.when(itemIndicatorIntMock2.getValue()).thenReturn(67); + Mockito.when(itemIndicatorIntMock2.getName()).thenReturn("indicador2"); + Mockito.when(itemIndicatorIntMock2.getUnit()).thenReturn("unidad2"); + Mockito.when(itemIndicatorIntMock2.getDescription()).thenReturn("Indicador Ejemplo 2"); + Mockito.when(itemIndicatorIntMock2.getDate()).thenReturn(fecha); + Mockito.when(indicatorIntMock2.getState()).thenReturn(IndicatorState.WARNING); + Mockito.when(itemIndicatorIntMock2.getIndicator()).thenReturn(indicatorIntMock2); + listaInd.add(itemIndicatorIntMock2); + + Mockito.when(itemIndicatorIntMock3.getValue()).thenReturn(98); + Mockito.when(itemIndicatorIntMock3.getName()).thenReturn("indicador3"); + Mockito.when(itemIndicatorIntMock3.getUnit()).thenReturn("unidad3"); + Mockito.when(itemIndicatorIntMock3.getDescription()).thenReturn("IndicadorEjemplo3"); + Mockito.when(itemIndicatorIntMock3.getDate()).thenReturn(fecha); + Mockito.when(indicatorIntMock3.getState()).thenReturn(IndicatorState.CRITICAL); + Mockito.when(itemIndicatorIntMock3.getIndicator()).thenReturn(indicatorIntMock3); + listaInd.add(itemIndicatorIntMock3); + + Mockito.when(informe.getAllMetrics()).thenReturn(listaMetric); + Mockito.when(informe.getAllIndicators()).thenReturn(listaInd); + Mockito.when(informe.getEntityId()).thenReturn("entidadTest"); + + excelPath = new String("src" + File.separator + "test" + File.separator + "resources" + File.separator); + excelName = new String("excelTest.xlsx"); + underTest = new ExcelReportManager(excelPath, excelName); + + underTest.setFormater(new ReportFormater()); + + log.info("El informe tiene el id " + informe.getEntityId()); + underTest.saveReport(informe); + + } + + /** + *

+ * Test para el m�todo de eliminar un informe en excel, solo verifica que si es + * null da excepcion + *

+ * + * @author Mariana Reyes Henriquez + */ + @Test + void ExcelDelete() { + excelPath = new String("src" + File.separator + "test" + File.separator + "resources" + File.separator); + excelName = new String("excelTest.xlsx"); + Mockito.when(informe.getEntityId()).thenReturn("entidadTest"); + + underTest = new ExcelReportManager(excelPath, excelName); + underTest.setFormater(new ReportFormater()); + + log.info("El informe tiene el id " + informe.getEntityId()); + underTest.saveReport(informe); + try { + log.info("Se intenta eliminar un informe que no existe"); + underTest.deleteReport(null); + fail("Debería dar excepcion"); + } catch (ReportNotDefinedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + try { + underTest.deleteReport(informe); + } catch (ReportNotDefinedException e) { + fail("No debería dar la excepción"); + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/src/test/java/us/muit/fs/a4i/test/persistence/ExcelReportWithRepositoryEnquirerTest.java b/src/test/java/us/muit/fs/a4i/test/persistence/ExcelReportWithRepositoryEnquirerTest.java new file mode 100644 index 00000000..f3699cf3 --- /dev/null +++ b/src/test/java/us/muit/fs/a4i/test/persistence/ExcelReportWithRepositoryEnquirerTest.java @@ -0,0 +1,31 @@ +package us.muit.fs.a4i.test.persistence; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.File; +import java.util.logging.Logger; + +import org.junit.jupiter.api.Test; + +import us.muit.fs.a4i.model.remote.GitHubRepositoryEnquirer; +import us.muit.fs.a4i.persistence.ExcelReportManager; +import us.muit.fs.a4i.persistence.ReportFormater; + +class ExcelReportWithRepositoryEnquirerTest { + private static Logger log = Logger.getLogger(ExcelReportWithRepositoryEnquirerTest.class.getName()); + + @Test + void testSaveRepositoryReport() { + String excelPath = new String("src" + File.separator + "test" + File.separator + "resources" + File.separator); + String excelName = new String("excelTest.xlsx"); + String repoName = new String("MIT-FS/Audit4Improve-API"); + ExcelReportManager underTest = new ExcelReportManager(excelPath, excelName); + + underTest.setFormater(new ReportFormater()); + + GitHubRepositoryEnquirer ghEnquirer = new GitHubRepositoryEnquirer(); + + underTest.saveReport(ghEnquirer.buildReport(repoName)); + } + +} diff --git a/src/test/java/us/muit/fs/a4i/test/persistence/package-info.java b/src/test/java/us/muit/fs/a4i/test/persistence/package-info.java index 130fd177..6ef560b9 100644 --- a/src/test/java/us/muit/fs/a4i/test/persistence/package-info.java +++ b/src/test/java/us/muit/fs/a4i/test/persistence/package-info.java @@ -1,7 +1,8 @@ /** - * Este paquete contiene las clases para probar las clases relacionadas con el modelo, en la api Audit4Improve + * Este paquete contiene las clases para probar las clases relacionadas con el + * modelo, en la api Audit4Improve * - * @author Isabel Román + * @author Isabel Román * @version 0.0 */ package us.muit.fs.a4i.test.persistence; \ No newline at end of file diff --git a/src/test/resources/appConfTest.json b/src/test/resources/appConfTest.json index 65c11a89..ba3bd76c 100644 --- a/src/test/resources/appConfTest.json +++ b/src/test/resources/appConfTest.json @@ -9,8 +9,32 @@ { "name": "comments", "type": "java.lang.Integer", - "description": "Númeoro de comentarios", + "description": "Número de comentarios", "unit": "comments" + }, + { + "name": "cycleTime", + "type": "java.lang.Double", + "description": "Tiempo medio que tarda una tarea en completarse", + "unit": "horas" + }, + { + "name": "waitTime", + "type": "java.lang.Double", + "description": "Tiempo medio que las tareas permanecen en espera o bloqueadas", + "unit": "horas" + }, + { + "name": "throughput", + "type": "java.lang.Double", + "description": "Número de tareas completadas en un período determinado", + "unit": "tareas/semana" + }, + { + "name": "WIP", + "type": "java.lang.Double", + "description": "Promedio de tareas en curso (trabajo en progreso)", + "unit": "tareas" } ], "indicators": [ @@ -18,13 +42,34 @@ "name": "pullReqGlory", "type": "java.lang.Double", "description": "Ratio de pull request aceptados frente a solicitados", - "unit": "ratio" + "unit": "ratio", + "limits": { + "ok": 2, + "warning": 4, + "critical": 6 + } }, { "name": "commentsInterest", "type": "java.lang.Double", - "description": "Ratio de comentarios con más de 1 respuesta frente al número total de comentarios", - "unit": "ratio" + "description": "Ratio de comentarios con más de 1 respuesta frente al número total de comentarios", + "unit": "ratio", + "limits": { + "ok": 2, + "warning": 4, + "critical": 6 + } + }, + { + "name": "IEF", + "type": "java.lang.Double", + "description": "Ãndice de Eficiencia del Flujo", + "unit": "índice", + "limits": { + "ok": 0.8, + "warning": 0.6, + "critical": 0.4 + } } ] } \ No newline at end of file diff --git a/src/test/resources/appTest.conf b/src/test/resources/appTest.conf new file mode 100644 index 00000000..7b35bf69 --- /dev/null +++ b/src/test/resources/appTest.conf @@ -0,0 +1,28 @@ +#Fichero de configuración establecido por la aplicación +#Tipo de persistencia que se quiere usar +persistence.type=WORD +#Tipo de remoto que se quiere usar +remote.type=GITLAB + + + +#Caracteristicas de fuente de las métricas +Font.metric.color=Red +Font.metric.height=15 +Font.metric.type=Arial + +#Caracteristicas para la fuente de indicadores según estados del mismo +Font.OK.color=Black +Font.OK.height=10 +Font.OK.type=Serif + +Font.WARNING.color=Orange +Font.WARNING.height=10 +Font.WARNING.type=Serif + +Font.CRITICAL.color=Red +Font.CRITICAL.height=20 +Font.CRITICAL.type=Courier New + +#Caracteristicas por defecto de la fuente, cuando los informes son visuales +#no lo modifico diff --git a/src/test/resources/excelTest.xlsx b/src/test/resources/excelTest.xlsx new file mode 100644 index 00000000..b7300c1b Binary files /dev/null and b/src/test/resources/excelTest.xlsx differ diff --git a/src/test/resources/log.properties b/src/test/resources/log.properties new file mode 100644 index 00000000..5e34c3d3 --- /dev/null +++ b/src/test/resources/log.properties @@ -0,0 +1,27 @@ +# para que este fichero de configuración de trazas sea efectivo es necesario pasarlo como parámetro de la máquina virtual al ejecutar +#-Djava.util.logging.config.file="src\test\resources\log.properties" +# especificacion de detalle de log +# nivel de log global +.level = OFF +# javax.json.Json.level = FINEST +us.muit.fs.a4i.model.remote.GitHubRepositoryEnquirer.level = INFO +us.muit.fs.a4i.persistence.ExcelReportManager.level = FINEST +us.muit.fs.a4i.config.Context.level=FINEST +us.muit.fs.a4i.config.Checker.level=FINEST +us.muit.fs.a4i.config.IndicatorConfiguration.level=FINEST +us.muit.fs.a4i.config.MetricConfiguration.level=FINEST +us.muit.fs.a4i.control.calculators.RepositoryCalculator.level = FINEST +# nivel clases + + +# manejadores de salida de log +# se cargaron un manejador de archivos y +# manejador de consola + +handlers = java.util.logging.ConsoleHandler + +# configuración de manejador de consola +# nivel soportado para consola +java.util.logging.ConsoleHandler.level = FINEST +# clase para formatear salida hacia consola +java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter