diff --git a/src/main/bundles/prod.bundle b/src/main/bundles/prod.bundle index 8a1c77b..2253e3a 100644 Binary files a/src/main/bundles/prod.bundle and b/src/main/bundles/prod.bundle differ diff --git a/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java b/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java index 78108c0..fdf682f 100644 --- a/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java +++ b/src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java @@ -49,6 +49,7 @@ public class RedmineClient { String statusFilter = includeClosed ? "&status_id=*" : "&status_id=open"; + String responseBody = null; while (true) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(REDMINE_URL + "/issues.json?key=" + user.getKey() + statusFilter + "&offset=" + offset)) @@ -58,45 +59,10 @@ public class RedmineClient { try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == 200) { - String responseBody = response.body(); + responseBody = response.body(); List pageTickets = parseTickets(responseBody); tickets.addAll(pageTickets); - if (pageTickets.size() < PAGE_SIZE) { - break; - } - offset += PAGE_SIZE; - } else { - System.err.println("Error en la respuesta: " + response.statusCode()); - break; - } - } catch (Exception e) { - e.printStackTrace(); - break; - } - } - System.out.println("Total tickets obtenidos: " + tickets.size()); - return tickets; - } - - //AQUI OBTENGO LOS TICKETS DESDE REDMINE - public List getTicketsAuthor(RedmineUser user) { - List tickets = new ArrayList<>(); - HttpClient client = HttpClient.newHttpClient(); - int offset = 0; - - while (true) { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(REDMINE_URL + "/issues.json?key=" + user.getKey() + "&author_id=" + user.getId() + "&offset=" + offset)) - .header("Content-Type", "application/json") - .build(); - - try { - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() == 200) { - String responseBody = response.body(); - List pageTickets = parseTickets(responseBody); - tickets.addAll(pageTickets); if (pageTickets.size() < PAGE_SIZE) { break; } @@ -165,6 +131,7 @@ public class RedmineClient { } + // Agrega el ticket a la lista tickets.add(new Ticket(id, subject, description, status, date != null ? date.toString() : "", trackerId)); } @@ -178,6 +145,42 @@ public class RedmineClient { return tickets; } + //AQUI OBTENGO LOS TICKETS DESDE REDMINE + public List getTicketsAuthor(RedmineUser user) { + List tickets = new ArrayList<>(); + HttpClient client = HttpClient.newHttpClient(); + int offset = 0; + + + while (true) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(REDMINE_URL + "/issues.json?key=" + user.getKey() + "&author_id=" + user.getId() + "&offset=" + offset)) + .header("Content-Type", "application/json") + .build(); + + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + String responseBody = response.body(); + List pageTickets = parseTickets(responseBody); + tickets.addAll(pageTickets); + if (pageTickets.size() < PAGE_SIZE) { + break; + } + offset += PAGE_SIZE; + } else { + System.err.println("Error en la respuesta: " + response.statusCode()); + break; + } + } catch (Exception e) { + e.printStackTrace(); + break; + } + } + System.out.println("Total tickets obtenidos: " + tickets.size()); + return tickets; + } + @@ -439,4 +442,42 @@ public class RedmineClient { return null; } } + + public List getTicketComments(int ticketId, RedmineUser user) { + List comments = new ArrayList<>(); + HttpClient client = HttpClient.newHttpClient(); + + String url = REDMINE_URL + "/issues/" + ticketId + ".json?include=journals&key=" + user.getKey(); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Content-Type", "application/json") + .build(); + + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == 200) { + String responseBody = response.body(); + JsonObject jsonResponse = JsonParser.parseString(responseBody).getAsJsonObject(); + JsonArray journals = jsonResponse.getAsJsonObject("issue").getAsJsonArray("journals"); + + for (JsonElement journalElement : journals) { + JsonObject journal = journalElement.getAsJsonObject(); + if (journal.has("notes") && !journal.get("notes").isJsonNull()) { + String note = journal.get("notes").getAsString().trim(); + if (!note.isEmpty()) { + comments.add(note); + } + } + } + } else { + System.err.println("Error en la respuesta al obtener los comentarios: " + response.statusCode()); + } + } catch (Exception e) { + e.printStackTrace(); + } + return comments; + } + } diff --git a/src/main/java/mx/gob/jumapacelaya/api/SecurityConfig.java b/src/main/java/mx/gob/jumapacelaya/api/SecurityConfig.java index fb692aa..a429a78 100644 --- a/src/main/java/mx/gob/jumapacelaya/api/SecurityConfig.java +++ b/src/main/java/mx/gob/jumapacelaya/api/SecurityConfig.java @@ -49,20 +49,22 @@ public class SecurityConfig extends VaadinWebSecurity { ) .formLogin(formLogin -> formLogin .loginPage("/login") - /*.successHandler((request, response, authentication) -> { - //String username = authentication.getName(); + .successHandler((request, response, authentication) -> { + String username = authentication.getName(); log.debug("Configure:loginSuccess: {}", authentication.getName()); - if (userService.getAuthenticatedRedmineUser() == null) { - log.debug("Configure:getAuthenticatedRedmineUser Error: {}", authentication.getName()); - response.sendRedirect("/login"); - } //else { - //response.sendRedirect(""); - //} - })*/ + var user = userService.getAuthenticatedRedmineUser(); + log.debug("Authenticated user: {}", user); + + if (user == null) { + log.warn("Usuario autenticado pero no encontrado en Redmine. Redirigiendo a página de error."); + response.sendRedirect(""); + } else { + response.sendRedirect(""); + } + }) .failureUrl("/login?error=true") // Corrigiendo la URL de fallo ); super.configure(http); - setLoginView(http, LoginView.class); } diff --git a/src/main/java/mx/gob/jumapacelaya/models/Ticket.java b/src/main/java/mx/gob/jumapacelaya/models/Ticket.java index bb4f9f4..cc825f7 100644 --- a/src/main/java/mx/gob/jumapacelaya/models/Ticket.java +++ b/src/main/java/mx/gob/jumapacelaya/models/Ticket.java @@ -4,6 +4,7 @@ package mx.gob.jumapacelaya.models; import java.sql.Date; import java.time.LocalDate; +import java.util.List; public class Ticket { @@ -15,6 +16,9 @@ public class Ticket { private LocalDate dateCreate; private User author; private Integer trackerId; + private List comments; + + public Ticket(int id, String subject, String description, String status, String dateCreate, Integer trackerId) { @@ -25,6 +29,7 @@ public class Ticket { this.dateCreate = LocalDate.parse(dateCreate); this.author = author; this.trackerId = trackerId; + this.comments = comments; } public int getId() { @@ -86,18 +91,26 @@ public class Ticket { } switch (trackerId) { - case 1,3,4,7: + case 5,7,8,11,12: return "1-2 dias Max"; - case 2,12: + case 2,14,16: return "2 hrs Max"; - case 5: + case 10: return "5 dias aprox."; - case 8,11: + case 15: return "2-6 hrs Max"; - case 9,10: + case 6,13: return "2 hrs Max"; default: return "N/A"; } } + + public List getComments() { + return comments; + } + + public void setComments(List comments) { + this.comments = comments; + } } diff --git a/src/main/java/mx/gob/jumapacelaya/services/UserService.java b/src/main/java/mx/gob/jumapacelaya/services/UserService.java index f8d12a1..b042bb2 100644 --- a/src/main/java/mx/gob/jumapacelaya/services/UserService.java +++ b/src/main/java/mx/gob/jumapacelaya/services/UserService.java @@ -1,5 +1,7 @@ package mx.gob.jumapacelaya.services; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.notification.NotificationVariant; import com.vaadin.flow.server.VaadinService; import mx.gob.jumapacelaya.api.RedmineClient; import mx.gob.jumapacelaya.api.SecurityService; @@ -39,8 +41,8 @@ public class UserService { CustomUserDetails userDetails = ldapService.getUserDetails(username); if (userDetails != null) { if (userDetails.getEmail() == null || userDetails.getEmail().isEmpty()) { - logger.error("El usuario: " + username + " no tiene correo electronico."); - return null; + logger.warn("El usuario {} no tiene correo electrónico. Asignando uno por defecto.", username); + userDetails.setEmail(username + "@jumapacelaya.gob.mx"); // Asigna un correo por defecto } RedmineUser newUser = RedmineClient.createRedmineUser( username, @@ -48,6 +50,7 @@ public class UserService { userDetails.getLastName(), userDetails.getEmail() ); + if (newUser != null) { logger.info("Usuario creado en Redmine: " + newUser); return newUser; @@ -89,8 +92,9 @@ public class UserService { } } }else{ - logger.error("No se pudo obtener al usuario autenticado en Redmine"); - securityService.logout(); + logger.error("No se pudo obtener al usuario autenticado en Redmine. Permitiendo acceso limitado"); + Notification.show("No se pudo obtener al usuario autenticado en Redmine. Permitiendo acceso limitado") + .addThemeVariants(NotificationVariant.LUMO_WARNING); } } return userclient; diff --git a/src/main/java/mx/gob/jumapacelaya/views/crearnuevoticket/CrearnuevoTicketView.java b/src/main/java/mx/gob/jumapacelaya/views/crearnuevoticket/CrearnuevoTicketView.java index f07da99..afaf8eb 100644 --- a/src/main/java/mx/gob/jumapacelaya/views/crearnuevoticket/CrearnuevoTicketView.java +++ b/src/main/java/mx/gob/jumapacelaya/views/crearnuevoticket/CrearnuevoTicketView.java @@ -31,10 +31,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; @Route(value = "", layout = MainLayout.class) @PermitAll @@ -159,13 +156,13 @@ public class CrearnuevoTicketView extends VerticalLayout { tipoTickets.setPlaceholder("Seleccione un tipo de ticket"); Map ticketTypesMap = api.getTicketTypes(); - Set ticketTypesSet = Set.of("Acceso/Permiso/Bajas", "Soporte de Software", "Capacitacion de Software", + /*Set ticketTypesSet = Set.of("Acceso/Permiso/Bajas", "Soporte de Software", "Capacitacion de Software", "Configuracion de Software", "Digitalizacion GIS", "Documento", "Funcionalidad", "Reporte", "Mantenimiento Correctivo", "Desarrollo de Software", "Actividad","Entrega de Consumibles","Instalación/Configuracion GIS"); List filteredTicketTypes = ticketTypesMap.keySet().stream() .filter(ticketTypesSet::contains) - .collect(Collectors.toList()); - tipoTickets.setItems(filteredTicketTypes); + .collect(Collectors.toList());*/ + tipoTickets.setItems(ticketTypesMap.keySet()); tipoTickets.addValueChangeListener(event -> { String selectedType = event.getValue(); @@ -240,7 +237,7 @@ public class CrearnuevoTicketView extends VerticalLayout { issueDetails.put("project_id", "soporte-tecnico-t-i"); issueDetails.put("subject", asunto.getValue()); String areaValue = area.getValue().trim(); - String descriptionCompleta = descripcion.getValue() + ("Area: " + areaValue + "\n\n"); // Concatenamos el valor del campo Area a la descripcion del ticket + String descriptionCompleta = descripcion.getValue() + (" \n" + " Area: " + areaValue + "\n\n"); // Concatenamos el valor del campo Area a la descripcion del ticket issueDetails.put("description", descriptionCompleta); issueDetails.put("tracker_id", selectedTrackerId); diff --git a/src/main/java/mx/gob/jumapacelaya/views/tickets/AllTicketsView.java b/src/main/java/mx/gob/jumapacelaya/views/tickets/AllTicketsView.java index 82d1074..c75c9cd 100644 --- a/src/main/java/mx/gob/jumapacelaya/views/tickets/AllTicketsView.java +++ b/src/main/java/mx/gob/jumapacelaya/views/tickets/AllTicketsView.java @@ -6,6 +6,8 @@ import com.vaadin.flow.component.dialog.Dialog; import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.GridVariant; import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; @@ -63,9 +65,10 @@ public class AllTicketsView extends VerticalLayout { grid.addColumn(ticket -> ticket.tiempoEst(ticket.getTrackerId())).setHeader("Tiempo estimado de atencion").setAutoWidth(false); grid.addComponentColumn(ticket -> { - Button btnVer = new Button("Ver"); + Button btnVer = new Button("Ver", new Icon(VaadinIcon.EYE)); + btnVer.setTooltipText("Ver descripción"); btnVer.addClickListener(event -> showDescription(ticket)); - btnVer.getStyle().set("color", "#691b31"); + btnVer.getStyle().set("color", "#A02142"); return btnVer; }).setHeader("Descripcion").setAutoWidth(true); @@ -98,11 +101,15 @@ public class AllTicketsView extends VerticalLayout { textEditor.setValue(ticket.getDescription()); textEditor.setReadOnly(true); + + Button verNotas = new Button("Comentarios"); + verNotas.addClickListener(event -> showComents(ticket)); + Button closeButton = new Button("Cerrar"); closeButton.addThemeVariants(ButtonVariant.LUMO_ERROR); closeButton.addClickListener(e -> dialog.close()); - HorizontalLayout buttonLayout = new HorizontalLayout(closeButton); + HorizontalLayout buttonLayout = new HorizontalLayout(verNotas, closeButton); buttonLayout.setWidthFull(); buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.END); buttonLayout.add(closeButton); @@ -112,6 +119,39 @@ public class AllTicketsView extends VerticalLayout { dialog.open(); } + private void showComents(Ticket ticket) { + Dialog comentDialog = new Dialog(); + comentDialog.getElement().setAttribute("arial-label", "Comentarios del ticket"); + comentDialog.setMaxHeight("500px"); + comentDialog.setMaxWidth("1100px"); + + VerticalLayout layout = new VerticalLayout(); + layout.setPadding(true); + layout.setSpacing(true); + + // Obtenemos los comentarios desde Proyman + List comentarios = redmineClient.getTicketComments(ticket.getId(), userService.getRedmineUser()); + + if (comentarios != null && !comentarios.isEmpty()) { + for (String comentario : comentarios) { + Span commentSpan = new Span(comentario); + commentSpan.getElement().getStyle().set("background", "#f1f1f1"); + commentSpan.getElement().getStyle().set("padding", "10px"); + commentSpan.getElement().getStyle().set("border-radius", "5px"); + layout.add(commentSpan); + } + } else { + layout.add(new Span("No hay comentarios para este ticket.")); + } + + Button closeButton = new Button("Cerrar", event -> comentDialog.close()); + closeButton.addThemeVariants(ButtonVariant.LUMO_ERROR); + + layout.add(closeButton); + comentDialog.add(layout); + comentDialog.open(); + } + private void loadTickets() { try { List tickets = redmineClient.getTickets(userService.getRedmineUser(), true); diff --git a/src/main/java/mx/gob/jumapacelaya/views/tickets/MisTicketsView.java b/src/main/java/mx/gob/jumapacelaya/views/tickets/MisTicketsView.java index 077c076..5ef122c 100644 --- a/src/main/java/mx/gob/jumapacelaya/views/tickets/MisTicketsView.java +++ b/src/main/java/mx/gob/jumapacelaya/views/tickets/MisTicketsView.java @@ -6,6 +6,8 @@ import com.vaadin.flow.component.dialog.Dialog; import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.GridVariant; import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; @@ -68,9 +70,10 @@ public class MisTicketsView extends VerticalLayout { grid.addColumn(ticket -> ticket.tiempoEst(ticket.getTrackerId())).setHeader("Tiempo estimado de atencion").setAutoWidth(false); grid.addComponentColumn(ticket -> { - Button btnVer = new Button("Ver"); + Button btnVer = new Button("Ver", new Icon(VaadinIcon.EYE)); + btnVer.setTooltipText("Ver descripción"); btnVer.addClickListener(event -> showDescription(ticket)); - btnVer.getStyle().set("color", "#691b31"); + btnVer.getStyle().set("color", "#A02142"); return btnVer; }).setHeader("Descripcion").setAutoWidth(true); @@ -102,11 +105,14 @@ public class MisTicketsView extends VerticalLayout { textEditor.setValue(ticket.getDescription()); textEditor.setReadOnly(true); + Button verNotas = new Button("Comentarios"); + verNotas.addClickListener(event -> showComents(ticket)); + Button closeButton = new Button("Cerrar"); closeButton.addThemeVariants(ButtonVariant.LUMO_ERROR); closeButton.addClickListener(e -> dialog.close()); - HorizontalLayout buttonLayout = new HorizontalLayout(closeButton); + HorizontalLayout buttonLayout = new HorizontalLayout(verNotas, closeButton); buttonLayout.setWidthFull(); buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.END); buttonLayout.add(closeButton); @@ -116,6 +122,39 @@ public class MisTicketsView extends VerticalLayout { dialog.open(); } + private void showComents(Ticket ticket) { + Dialog comentDialog = new Dialog(); + comentDialog.getElement().setAttribute("arial-label", "Comentarios del ticket"); + comentDialog.setMaxHeight("500px"); + comentDialog.setMaxWidth("1100px"); + + VerticalLayout layout = new VerticalLayout(); + layout.setPadding(true); + layout.setSpacing(true); + + // Obtenemos los comentarios desde Proyman + List comentarios = redmineClient.getTicketComments(ticket.getId(), userService.getRedmineUser()); + + if (comentarios != null && !comentarios.isEmpty()) { + for (String comentario : comentarios) { + Span commentSpan = new Span(comentario); + commentSpan.getElement().getStyle().set("background", "#f1f1f1"); + commentSpan.getElement().getStyle().set("padding", "10px"); + commentSpan.getElement().getStyle().set("border-radius", "5px"); + layout.add(commentSpan); + } + } else { + layout.add(new Span("No hay comentarios para este ticket.")); + } + + Button closeButton = new Button("Cerrar", event -> comentDialog.close()); + closeButton.addThemeVariants(ButtonVariant.LUMO_ERROR); + + layout.add(closeButton); + comentDialog.add(layout); + comentDialog.open(); + } + private void loadTickets() { try { List tickets = redmineClient.getTicketsAuthor(userService.getRedmineUser()); @@ -132,13 +171,22 @@ public class MisTicketsView extends VerticalLayout { Span span = new Span(ticket.getStatus()); switch (ticket.getStatus().toLowerCase()) { case "análisis": - span.getElement().getStyle().set("color","purple"); + span.getElement().getStyle().set("color","orange"); break; case "desarrollo": - span.getElement().getStyle().set("color","green"); + span.getElement().getStyle().set("color","blue"); + break; + case "rechazada": + span.getElement().getStyle().set("color","red"); + break; + case "cerrada": + span.getElement().getStyle().set("color","grey"); + break; + case "solicitado": + span.getElement().getStyle().set("color","purple"); break; default: - span.getElement().getStyle().set("color","blue"); + span.getElement().getStyle().set("color","green"); break; } return span; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4e1161b..5997237 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -34,4 +34,4 @@ spring.servlet.multipart.max-request-size=10MB ##############TIEMPO DE DURACION DE LA SESION################# server.servlet.session.timeout=30m -vaadin.session.expiration=30m +server.servlet.session.cookie.secure=true