Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion stratify/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.7.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.quantum.stratify.config;


import com.quantum.stratify.config.jwt.JwtAuthenticationEntryPoint;
import com.quantum.stratify.config.jwt.JwtAuthorizationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
Expand All @@ -17,6 +15,9 @@
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import com.quantum.stratify.config.jwt.JwtAuthenticationEntryPoint;
import com.quantum.stratify.config.jwt.JwtAuthorizationFilter;


@EnableMethodSecurity
@EnableWebMvc
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.quantum.stratify.services;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.stereotype.Service;

import com.opencsv.CSVWriter;
import com.quantum.stratify.web.dtos.PercentualStatusUsuarioDTO;
import com.quantum.stratify.web.dtos.QuantidadeCardsPorSprintDTO;
import com.quantum.stratify.web.dtos.QuantidadeCardsPorTagDTO;
import com.quantum.stratify.web.dtos.ResponseQuantidadeCardsByPeriodo;
import com.quantum.stratify.web.dtos.TempoMedioPorProjetoDTO;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@RequiredArgsConstructor
@Slf4j
public class CsvDownloadService {

@Autowired
private UserStoryService userStoryService;

@Autowired
private FatoUserStoryTemporaisService fatoUserStoryTemporaisService;

@Autowired
private UserStoryTagService userStoryTagService;

@Autowired
private UserStoryStatusService userStoryStatusService;

@Autowired
private FatoEficienciaUserStoryService fatoEficienciaUserStoryService;
public InputStreamResource generateCsv(Long projectId, Long userId, String exportType) {
List<String[]> csvData;

switch (exportType.toLowerCase()) {
case "cardsporsprint": {
List<QuantidadeCardsPorSprintDTO> lista = userStoryService.getQuantidadeUserStoriesBySprint(projectId, userId);
csvData = new ArrayList<>();
csvData.add(new String[]{"Sprint", "Quantidade"});
for (QuantidadeCardsPorSprintDTO dto : lista) {
csvData.add(new String[]{dto.getSprint(), String.valueOf(dto.getQuantidade())});
}
log.info("ExportType=cardsporsprint → Encontrados {} registros para projeto={} usuário={}", lista.size(), projectId, userId);
break;
}
case "cardsporperiodo": {
List<ResponseQuantidadeCardsByPeriodo> lista = fatoUserStoryTemporaisService.getUserStoriesByPeriodoAndUser(projectId, userId);
csvData = new ArrayList<>();
csvData.add(new String[]{"Período", "Criadas", "Finalizadas"});
for (ResponseQuantidadeCardsByPeriodo dto : lista) {
csvData.add(new String[]{
dto.periodo(),
String.valueOf(dto.quantidadeCriadas()),
String.valueOf(dto.quantidadeFinalizadas())
});
}
break;
}
case "cardsporetiqueta": {
List<QuantidadeCardsPorTagDTO> lista = userStoryTagService.getQuantidadeUserStoriesByTag(projectId, userId);
csvData = new ArrayList<>();
csvData.add(new String[]{"Tag", "Quantidade"});
for (QuantidadeCardsPorTagDTO dto : lista) {
csvData.add(new String[]{dto.getNomeTag(), String.valueOf(dto.getQuantidade())});
}
break;
}
case "cardsporstatus": {
List<PercentualStatusUsuarioDTO> lista = userStoryStatusService.getPercentualUserStoriesPorStatus(projectId, userId);
csvData = new ArrayList<>();
csvData.add(new String[]{"Status", "Percentual"});
for (PercentualStatusUsuarioDTO dto : lista) {
csvData.add(new String[]{dto.getNomeStatus(), String.valueOf(dto.getPercentual())});
}
break;
}
case "tempomedio": {
List<TempoMedioPorProjetoDTO> lista = fatoEficienciaUserStoryService.getTempoMedioFiltrado(projectId, userId);
csvData = new ArrayList<>();
csvData.add(new String[]{"ID User Story", "Descrição", "Tempo Médio", "Quantidade Retrabalho"});
for (TempoMedioPorProjetoDTO dto : lista) {
csvData.add(new String[]{
String.valueOf(dto.idUserStory()),
dto.descricao(),
String.valueOf(dto.tempoMedio()),
String.valueOf(dto.quantidadeRetrabalhos())
});
}
break;
}
default:
throw new IllegalArgumentException("Tipo de exportação inválido: " + exportType);
}

ByteArrayOutputStream out = new ByteArrayOutputStream();
try (CSVWriter writer = new CSVWriter(new OutputStreamWriter(out))) {
for (String[] line : csvData) {
writer.writeNext(line);
}
} catch (IOException e) {
throw new RuntimeException("Erro ao gerar CSV", e);
}

return new InputStreamResource(new ByteArrayInputStream(out.toByteArray()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.quantum.stratify.web.controllers;

import java.io.IOException;

import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.quantum.stratify.services.CsvDownloadService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RequiredArgsConstructor
@RestController
@RequestMapping("/download")
@CrossOrigin("*")
@Slf4j
@Tag(name = "CSV Download", description = "Exportação de relatórios em formato CSV")
public class CsvDownloadController {

private final CsvDownloadService csvDownloadService;

@Operation(summary = "Exporta dados filtrados em formato CSV")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Arquivo CSV gerado com sucesso"),
@ApiResponse(responseCode = "400", description = "Parâmetros inválidos"),
@ApiResponse(responseCode = "404", description = "Dados não encontrados"),
@ApiResponse(responseCode = "500", description = "Erro interno ao gerar o CSV")
})
@GetMapping("/csv")
public ResponseEntity<InputStreamResource> downloadCsv(
@Parameter(description = "Tipo de exportação (ex: cardsporsprint, cardsporperiodo, etc.)", required = true)
@RequestParam String exportType,

@Parameter(description = "ID do projeto (opcional)")
@RequestParam(required = false) Long projectId,

@Parameter(description = "ID do usuário (opcional)")
@RequestParam(required = false) Long userId
) throws IOException {

String filename = exportType + ".csv";
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename);
headers.add(HttpHeaders.CONTENT_TYPE, "text/csv");

InputStreamResource resource = csvDownloadService.generateCsv(projectId, userId, exportType);
return new ResponseEntity<>(resource, headers, HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.quantum.stratify.services;

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

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.core.io.InputStreamResource;

import com.quantum.stratify.web.dtos.QuantidadeCardsPorSprintDTO;
import com.quantum.stratify.web.dtos.ResponseQuantidadeCardsByPeriodo;

class CsvDownloadServiceTest {

@Mock
private UserStoryService userStoryService;
@Mock
private FatoUserStoryTemporaisService fatoUserStoryTemporaisService;
@Mock
private UserStoryTagService userStoryTagService;
@Mock
private UserStoryStatusService userStoryStatusService;
@Mock
private FatoEficienciaUserStoryService fatoEficienciaUserStoryService;

@InjectMocks
private CsvDownloadService csvDownloadService;

@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}

@Test
void shouldGenerateCsvForCardsporsprint() throws Exception {
when(userStoryService.getQuantidadeUserStoriesBySprint(1L, 1L)).thenReturn(
List.of(new QuantidadeCardsPorSprintDTO("Sprint 1", 5L))
);

InputStreamResource result = csvDownloadService.generateCsv(1L, 1L, "cardsporsprint");
BufferedReader reader = new BufferedReader(new InputStreamReader(result.getInputStream()));

assertEquals("\"Sprint\",\"Quantidade\"", reader.readLine());
assertEquals("\"Sprint 1\",\"5\"", reader.readLine());
}

@Test
void shouldGenerateCsvForCardsporperiodo() throws Exception {
when(fatoUserStoryTemporaisService.getUserStoriesByPeriodoAndUser(1L, 1L)).thenReturn(
List.of(new ResponseQuantidadeCardsByPeriodo("2024-01", 3, 2))
);

InputStreamResource result = csvDownloadService.generateCsv(1L, 1L, "cardsporperiodo");
BufferedReader reader = new BufferedReader(new InputStreamReader(result.getInputStream()));

assertEquals("\"Período\",\"Criadas\",\"Finalizadas\"", reader.readLine());
assertEquals("\"2024-01\",\"3\",\"2\"", reader.readLine());
}

@Test
void shouldThrowForInvalidExportType() {
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
() -> csvDownloadService.generateCsv(1L, 1L, "invalido"));
assertEquals("Tipo de exportação inválido: invalido", ex.getMessage());
}
}