Browse Source

Se arreglo el error de que no mostraba las horas correctamente en las columnas de fechas, y tambien ya funiciona bien el checkBox para filtrar por tickets cerrados o abiertos

main
mramirezg 2 months ago
parent
commit
5a77ec8afa
4 changed files with 114 additions and 107 deletions
  1. BIN
      src/main/bundles/dev.bundle
  2. +68
    -57
      src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java
  3. +10
    -9
      src/main/java/mx/gob/jumapacelaya/models/Ticket.java
  4. +36
    -41
      src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java

BIN
src/main/bundles/dev.bundle View File


+ 68
- 57
src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java View File

@ -3,6 +3,8 @@ package mx.gob.jumapacelaya.api;
import com.google.gson.*; import com.google.gson.*;
import mx.gob.jumapacelaya.models.RedmineUser; import mx.gob.jumapacelaya.models.RedmineUser;
import mx.gob.jumapacelaya.models.Ticket; import mx.gob.jumapacelaya.models.Ticket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -12,6 +14,9 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
import java.util.ArrayList; import java.util.ArrayList;
@ -21,11 +26,11 @@ import java.util.Map;
@Component @Component
public class RedmineClient { public class RedmineClient {
private static final Logger log = LoggerFactory.getLogger(RedmineClient.class);
private static final int PAGE_SIZE = 25;
//private static final int PAGE_SIZE = 25;
static String REDMINE_URL; static String REDMINE_URL;
static String API_KEY; static String API_KEY;
public static final Gson GSON = new Gson();
public RedmineClient(@Value("${redmine.url}") String redmineUrl, @Value("${redmine.api_key}") String apiKey) { public RedmineClient(@Value("${redmine.url}") String redmineUrl, @Value("${redmine.api_key}") String apiKey) {
REDMINE_URL = redmineUrl; REDMINE_URL = redmineUrl;
@ -52,7 +57,9 @@ public class RedmineClient {
try { try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) { if (response.statusCode() == 200) {
tickets.addAll(parseTickets(response.body()));
String responseBody = response.body();
log.info(responseBody);
tickets.addAll(parseTickets(responseBody));
} else { } else {
System.err.println("Error en la respuesta: " + response.statusCode()); System.err.println("Error en la respuesta: " + response.statusCode());
} }
@ -133,16 +140,12 @@ public class RedmineClient {
for (JsonElement issueElement : issues) { for (JsonElement issueElement : issues) {
JsonObject issue = issueElement.getAsJsonObject(); JsonObject issue = issueElement.getAsJsonObject();
// Verifica y obtiene el ID
// ID, subject, descripción
int id = issue.has("id") && !issue.get("id").isJsonNull() ? issue.get("id").getAsInt() : 0; int id = issue.has("id") && !issue.get("id").isJsonNull() ? issue.get("id").getAsInt() : 0;
// Verifica y obtiene el subject
String subject = issue.has("subject") && !issue.get("subject").isJsonNull() ? issue.get("subject").getAsString() : ""; String subject = issue.has("subject") && !issue.get("subject").isJsonNull() ? issue.get("subject").getAsString() : "";
// Verifica y obtiene la descripción
String description = issue.has("description") && !issue.get("description").isJsonNull() ? issue.get("description").getAsString() : ""; String description = issue.has("description") && !issue.get("description").isJsonNull() ? issue.get("description").getAsString() : "";
// Verifica y obtiene el status
// Status
String status = "Unknown"; String status = "Unknown";
if (issue.has("status") && !issue.get("status").isJsonNull()) { if (issue.has("status") && !issue.get("status").isJsonNull()) {
JsonObject statusObject = issue.getAsJsonObject("status"); JsonObject statusObject = issue.getAsJsonObject("status");
@ -151,57 +154,21 @@ public class RedmineClient {
} }
} }
// Verifica y obtiene la fecha de creación
String dateString = issue.has("created_on") && !issue.get("created_on").isJsonNull() ? issue.get("created_on").getAsString() : "";
LocalDate dateCreate = null;
if (!dateString.isEmpty()) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
dateCreate = LocalDate.parse(dateString, formatter);
} catch (DateTimeParseException e) {
System.err.println("Error al parsear la fecha: " + dateString);
e.printStackTrace();
}
}
// Verifica y obtiene la fecha de cierre
String closeDateString = issue.has("closed_on") && !issue.get("closed_on").isJsonNull() ? issue.get("closed_on").getAsString() : "";
LocalDate dateClose = null;
if (!closeDateString.isEmpty()) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
dateClose = LocalDate.parse(closeDateString, formatter);
} catch (DateTimeParseException e) {
System.err.println("Error al parsear la fecha de cierre: " + closeDateString);
e.printStackTrace();
}
}
// Verifica y obtiene la fecha de actualizacion
String updateDateString = issue.has("updated_on") && !issue.get("updated_on").isJsonNull() ? issue.get("updated_on").getAsString() : "";
LocalDate dateUpdate = null;
if (!updateDateString.isEmpty()) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
dateUpdate = LocalDate.parse(updateDateString, formatter);
} catch (DateTimeParseException e) {
System.err.println("Error al parsear la fecha de actualización: " + updateDateString);
e.printStackTrace();
}
}
// Parse fechas correctamente
LocalDateTime dateCreate = parseDateTime(issue, "created_on");
LocalDateTime dateUpdate = parseDateTime(issue, "updated_on");
LocalDateTime dateClose = parseDateTime(issue, "closed_on");
// Autor
Ticket.User autor = null; Ticket.User autor = null;
if (issue.has("author") && !issue.get("author").isJsonNull()) { if (issue.has("author") && !issue.get("author").isJsonNull()) {
JsonObject authorObj = issue.getAsJsonObject("author"); JsonObject authorObj = issue.getAsJsonObject("author");
if (authorObj.has("name") && !authorObj.get("name").isJsonNull()) { if (authorObj.has("name") && !authorObj.get("name").isJsonNull()) {
String authorName = authorObj.get("name").getAsString();
autor = new Ticket.User(authorName);
autor = new Ticket.User(authorObj.get("name").getAsString());
} }
} }
//Verifica y obtiene el ID del tipo de ticket
// Tracker ID
Integer trackerId = null; Integer trackerId = null;
if (issue.has("tracker") && !issue.get("tracker").isJsonNull()) { if (issue.has("tracker") && !issue.get("tracker").isJsonNull()) {
JsonObject trackerObject = issue.getAsJsonObject("tracker"); JsonObject trackerObject = issue.getAsJsonObject("tracker");
@ -210,14 +177,24 @@ public class RedmineClient {
} }
} }
// Agrega el ticket a la lista
tickets.add(new Ticket(id, subject, description, status,
// Crear ticket
Ticket ticketObj = new Ticket(
id,
subject,
description,
status,
dateCreate != null ? dateCreate.toString() : "", dateCreate != null ? dateCreate.toString() : "",
dateClose != null ? dateClose.toString() : "", dateClose != null ? dateClose.toString() : "",
dateUpdate != null ? dateUpdate.toString() : "", dateUpdate != null ? dateUpdate.toString() : "",
autor, autor,
trackerId, "Tipo Desconocido"));
trackerId,
"Tipo Desconocido"
);
// Log para verificar JSON de ticket
//System.out.println(GSON.toJson(ticketObj));
tickets.add(ticketObj);
} }
} else { } else {
System.out.println("La respuesta JSON no contiene la clave 'issues'"); System.out.println("La respuesta JSON no contiene la clave 'issues'");
@ -229,6 +206,7 @@ public class RedmineClient {
return tickets; return tickets;
} }
public RedmineUser getMyAccount(String username) { public RedmineUser getMyAccount(String username) {
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(REDMINE_URL + "/my/account.json")) .uri(URI.create(REDMINE_URL + "/my/account.json"))
@ -306,7 +284,7 @@ public class RedmineClient {
public RedmineUser getUserByUsername(String username) { public RedmineUser getUserByUsername(String username) {
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(REDMINE_URL + "/users.json?name=" + username ))
.uri(URI.create(REDMINE_URL + "/users.json?name=" + username))
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("X-Redmine-API-Key", API_KEY) .header("X-Redmine-API-Key", API_KEY)
.build(); .build();
@ -369,4 +347,37 @@ public class RedmineClient {
System.err.println(response.body()); System.err.println(response.body());
} }
} }
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(LocalDateTime.class,
(JsonSerializer<LocalDateTime>) (src, typeOfSrc, context) ->
new JsonPrimitive(src.toString()))
.registerTypeAdapter(LocalDateTime.class,
(JsonDeserializer<LocalDateTime>) (json, typeOf, context) ->
LocalDateTime.parse(json.getAsString()))
.setPrettyPrinting()
.create();
// Método auxiliar para parsear echas con hora
private LocalDateTime parseDateTime(JsonObject issue, String fieldName) {
if (!issue.has(fieldName) || issue.get(fieldName).isJsonNull()) {
return null;
}
String dateStr = issue.get(fieldName).getAsString();
try {
// Si viene con 'Z' al final (UTC), convertir a LocalDateTime
if (dateStr.endsWith("Z")) {
return OffsetDateTime.parse(dateStr)
.atZoneSameInstant(ZoneId.systemDefault())
.toLocalDateTime();
} else {
return LocalDateTime.parse(dateStr);
}
} catch (DateTimeParseException ex) {
System.err.println("Error al parsear fecha '" + fieldName + "': " + dateStr);
return null;
}
}
} }

+ 10
- 9
src/main/java/mx/gob/jumapacelaya/models/Ticket.java View File

@ -5,6 +5,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
public class Ticket { public class Ticket {
private final int id; private final int id;
@ -49,16 +50,16 @@ public class Ticket {
return null; return null;
} }
try { try {
OffsetDateTime odt = OffsetDateTime.parse(dateStr);
return odt.atZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
} catch (Exception e) {
try {
LocalDate ld = LocalDate.parse(dateStr);
return ld.atStartOfDay();
} catch (Exception ex) {
System.err.println("Error al parsear fecha: " + dateStr);
return null;
if (dateStr.endsWith("Z")) {
return OffsetDateTime.parse(dateStr)
.atZoneSameInstant(ZoneId.systemDefault())
.toLocalDateTime();
} else {
return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"));
} }
} catch (Exception e) {
System.err.println("Error al parsear fecha: " + dateStr);
return null;
} }
} }


+ 36
- 41
src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java View File

@ -60,7 +60,7 @@ public class ActDiariaView extends VerticalLayout {
private final HorizontalLayout showColumnsLyt; private final HorizontalLayout showColumnsLyt;
private final Button btnColumns; private final Button btnColumns;
private final HorizontalLayout opcionesLyt; private final HorizontalLayout opcionesLyt;
private final Checkbox chkVerCerrados;
private Checkbox chkSoloAbiertos;
private DatePicker fechaDesde; private DatePicker fechaDesde;
private DatePicker fechaHasta; private DatePicker fechaHasta;
private Button btnBuscar; private Button btnBuscar;
@ -72,7 +72,7 @@ public class ActDiariaView extends VerticalLayout {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
// Configuracion de las opciones de arriba
// Layout de opciones
opcionesLyt = new HorizontalLayout(); opcionesLyt = new HorizontalLayout();
opcionesLyt.setWidthFull(); opcionesLyt.setWidthFull();
opcionesLyt.setMargin(false); opcionesLyt.setMargin(false);
@ -83,26 +83,33 @@ public class ActDiariaView extends VerticalLayout {
.set("padding", "1rem") .set("padding", "1rem")
.set("margin", "1rem auto"); .set("margin", "1rem auto");
// Checkbox para ver solo abiertos
chkSoloAbiertos = new Checkbox("Ver solo abiertos");
chkSoloAbiertos.setValue(false); // inicial: mostrar cerrados
chkVerCerrados = new Checkbox();
chkVerCerrados.setLabel("Ver solo abiertos");
chkVerCerrados.addValueChangeListener(e -> {
boolean soloAbiertos = e.getValue().equals(false);
fechaDesde = new DatePicker("Fecha desde:");
fechaHasta = new DatePicker("Fecha hasta:");
btnBuscar = new Button("Buscar");
// Listener del checkbox
chkSoloAbiertos.addValueChangeListener(e -> {
boolean soloAbiertos = e.getValue(); // marcado = abiertos
loadTickets(soloAbiertos); loadTickets(soloAbiertos);
fechaDesde.setEnabled(false);
fechaHasta.setEnabled(false);
btnBuscar.setEnabled(false);
fechaDesde.setEnabled(!soloAbiertos);
fechaHasta.setEnabled(!soloAbiertos);
btnBuscar.setEnabled(!soloAbiertos);
}); });
fechaDesde = new DatePicker("Fecha desde:");
// Estado inicial
fechaDesde.setEnabled(true); fechaDesde.setEnabled(true);
fechaHasta = new DatePicker("Fecha hasta:");
fechaHasta.setEnabled(true); fechaHasta.setEnabled(true);
btnBuscar.setEnabled(true);
btnBuscar = new Button("Buscar");
opcionesLyt.add(chkVerCerrados,fechaDesde,fechaHasta,btnBuscar);
// Cargar tickets iniciales (cerrados)
loadTickets(false);
opcionesLyt.add(chkSoloAbiertos, fechaDesde, fechaHasta, btnBuscar);
// Configuración de columnas del grid // Configuración de columnas del grid
grid.addColumn(Ticket::getId).setHeader("No.") grid.addColumn(Ticket::getId).setHeader("No.")
@ -114,19 +121,16 @@ public class ActDiariaView extends VerticalLayout {
.setAutoWidth(true) .setAutoWidth(true)
.setKey("tipo"); .setKey("tipo");
grid.addColumn(ticket -> ticket.tiempoEst(ticket.getTrackerId())) grid.addColumn(ticket -> ticket.tiempoEst(ticket.getTrackerId()))
.setHeader("Tiempo estimado") .setHeader("Tiempo estimado")
.setAutoWidth(true) .setAutoWidth(true)
.setKey("tiempoEst"); .setKey("tiempoEst");
grid.addColumn(Ticket::getSubject) grid.addColumn(Ticket::getSubject)
.setHeader("Asunto") .setHeader("Asunto")
.setWidth("25rem") .setWidth("25rem")
.setKey("asunto"); .setKey("asunto");
grid.addColumn(ticket -> grid.addColumn(ticket ->
ticket.getAuthor() != null ? ticket.getAuthor().getUsername() : "") ticket.getAuthor() != null ? ticket.getAuthor().getUsername() : "")
.setHeader("Autor") .setHeader("Autor")
@ -138,26 +142,25 @@ public class ActDiariaView extends VerticalLayout {
grid.addColumn(ticket -> { grid.addColumn(ticket -> {
LocalDateTime fecha = ticket.getDateCreate(); LocalDateTime fecha = ticket.getDateCreate();
return fecha != null ? fecha.format(formatter) : "";
return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : "";
}).setHeader("Fecha creación") }).setHeader("Fecha creación")
.setAutoWidth(true) .setAutoWidth(true)
.setKey("fechaCreacion"); .setKey("fechaCreacion");
grid.addColumn(ticket -> { grid.addColumn(ticket -> {
LocalDateTime fecha = ticket.getDateClose(); LocalDateTime fecha = ticket.getDateClose();
return fecha != null ? fecha.format(formatter) : "";
return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : "";
}).setHeader("Fecha cierre") }).setHeader("Fecha cierre")
.setAutoWidth(true) .setAutoWidth(true)
.setKey("fechaCierre"); .setKey("fechaCierre");
grid.addColumn(ticket -> { grid.addColumn(ticket -> {
LocalDateTime fecha = ticket.getUpdateOn(); LocalDateTime fecha = ticket.getUpdateOn();
return fecha != null ? fecha.format(formatter) : "";
return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : "";
}).setHeader("Fecha actualización") }).setHeader("Fecha actualización")
.setAutoWidth(true) .setAutoWidth(true)
.setKey("fechaActualizacion"); .setKey("fechaActualizacion");
grid.addColumn(ticket -> { grid.addColumn(ticket -> {
if (ticket.getDateCreateLocal() != null) { if (ticket.getDateCreateLocal() != null) {
LocalDateTime fechaInicio = ticket.getDateCreateLocal(); LocalDateTime fechaInicio = ticket.getDateCreateLocal();
@ -171,7 +174,6 @@ public class ActDiariaView extends VerticalLayout {
} }
}).setHeader("Duración").setAutoWidth(true).setKey("duracion"); }).setHeader("Duración").setAutoWidth(true).setKey("duracion");
grid.addComponentColumn(ticket -> { grid.addComponentColumn(ticket -> {
Button btnVer = new Button(new Icon(VaadinIcon.EYE)); Button btnVer = new Button(new Icon(VaadinIcon.EYE));
btnVer.addClickListener(event -> showDescription(ticket)); btnVer.addClickListener(event -> showDescription(ticket));
@ -179,18 +181,13 @@ public class ActDiariaView extends VerticalLayout {
return btnVer; return btnVer;
}); });
//grid.addColumn(buttonTicketComponentRenderer()).setAutoWidth(true);
grid.addThemeVariants(GridVariant.LUMO_WRAP_CELL_CONTENT); grid.addThemeVariants(GridVariant.LUMO_WRAP_CELL_CONTENT);
grid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES); grid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES);
//grid.getStyle().set("opacity", "0.8");
//grid.setAllRowsVisible(false);
grid.setSizeFull(); grid.setSizeFull();
// Mostrar boton para elegir que columnas mostrar
/**/btnColumns = new Button(VaadinIcon.GRID_H.create());
/**/showColumnsLyt = new HorizontalLayout(btnColumns);
// Botón para mostrar/ocultar columnas
btnColumns = new Button(VaadinIcon.GRID_H.create());
showColumnsLyt = new HorizontalLayout(btnColumns);
HorizontalLayout columnsSelectorLyt = new HorizontalLayout(); HorizontalLayout columnsSelectorLyt = new HorizontalLayout();
columnsSelectorLyt.setAlignItems(Alignment.END); columnsSelectorLyt.setAlignItems(Alignment.END);
Popover popover = new Popover(); Popover popover = new Popover();
@ -223,43 +220,41 @@ public class ActDiariaView extends VerticalLayout {
Set<String> defaultColumns = Set.of("tipo","asunto","autor","estado","fechaCreacion","fechaCierre"); Set<String> defaultColumns = Set.of("tipo","asunto","autor","estado","fechaCreacion","fechaCierre");
chkColumns.setValue(defaultColumns); chkColumns.setValue(defaultColumns);
popover.add(heading, chkColumns); popover.add(heading, chkColumns);
/****/
// Ajustar tamaño del Grid y Layout
// Ajustar tamaños
grid.setSizeFull(); grid.setSizeFull();
setSizeFull(); setSizeFull();
add(opcionesLyt, showColumnsLyt, grid); add(opcionesLyt, showColumnsLyt, grid);
//expand(grid);
setMargin(false); setMargin(false);
loadTickets(true);
} }
// NOTA: Ajuste en la carga de tickets para que ahora haga LazyLoad al cargar todos los ticktes cerrados // NOTA: Ajuste en la carga de tickets para que ahora haga LazyLoad al cargar todos los ticktes cerrados
private void loadTickets(boolean soloAbiertos) { private void loadTickets(boolean soloAbiertos) {
RedmineUser user = userService.getRedmineUser(); RedmineUser user = userService.getRedmineUser();
// Invertir parámetro para que coincida con RedmineClient
boolean paramCliente = !soloAbiertos;
grid.setItems( grid.setItems(
query -> { query -> {
int offset = query.getOffset(); int offset = query.getOffset();
int limit = query.getLimit(); int limit = query.getLimit();
try { try {
List<Ticket> ticketsPage = redmineClient.getTickets(user, soloAbiertos, offset, limit);
List<Ticket> ticketsPage = redmineClient.getTickets(user, paramCliente, offset, limit);
return ticketsPage.stream(); return ticketsPage.stream();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return Stream.empty();
return Stream.empty();
} }
}, },
query -> {
return redmineClient.getTotalTickets(user, soloAbiertos);
}
query -> redmineClient.getTotalTickets(user, paramCliente)
); );
} }
private ComponentRenderer<Span, Ticket> createStatusRender() { private ComponentRenderer<Span, Ticket> createStatusRender() {
return new ComponentRenderer<>(ticket -> { return new ComponentRenderer<>(ticket -> {
// Creamos un Span para mostrar el estado // Creamos un Span para mostrar el estado


Loading…
Cancel
Save