mirror of
https://github.com/dalbodeule/snap-admin.git
synced 2025-06-08 21:38:21 +00:00
WIP CSV export
This commit is contained in:
parent
a86a369120
commit
7250a2433c
83
src/main/java/tech/ailef/dbadmin/external/controller/DataExportController.java
vendored
Normal file
83
src/main/java/tech/ailef/dbadmin/external/controller/DataExportController.java
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package tech.ailef.dbadmin.external.controller;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.apache.commons.csv.CSVFormat;
|
||||||
|
import org.apache.commons.csv.CSVPrinter;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import tech.ailef.dbadmin.external.DbAdmin;
|
||||||
|
import tech.ailef.dbadmin.external.dbmapping.DbAdminRepository;
|
||||||
|
import tech.ailef.dbadmin.external.dbmapping.DbField;
|
||||||
|
import tech.ailef.dbadmin.external.dbmapping.DbObject;
|
||||||
|
import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema;
|
||||||
|
import tech.ailef.dbadmin.external.dto.QueryFilter;
|
||||||
|
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
|
||||||
|
import tech.ailef.dbadmin.external.misc.Utils;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping(value= {"/${dbadmin.baseUrl}/export", "/${dbadmin.baseUrl}/export/"})
|
||||||
|
public class DataExportController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DbAdmin dbAdmin;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DbAdminRepository repository;
|
||||||
|
|
||||||
|
@GetMapping("/{className}")
|
||||||
|
@ResponseBody
|
||||||
|
public ResponseEntity<byte[]> export(@PathVariable String className,
|
||||||
|
@RequestParam(required=false) String query,
|
||||||
|
@RequestParam MultiValueMap<String, String> otherParams) {
|
||||||
|
|
||||||
|
DbObjectSchema schema = dbAdmin.findSchemaByClassName(className);
|
||||||
|
|
||||||
|
Set<QueryFilter> queryFilters = Utils.computeFilters(schema, otherParams);
|
||||||
|
|
||||||
|
System.out.println("QF = " + queryFilters);
|
||||||
|
|
||||||
|
List<DbObject> results = repository.search(schema, query, queryFilters);
|
||||||
|
|
||||||
|
String result = toCsv(results, schema.getSortedFields());
|
||||||
|
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
|
||||||
|
"attachment; filename=\"export_" + schema.getClass().getSimpleName() + ".csv\"")
|
||||||
|
.body(result.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toCsv(List<DbObject> items, List<DbField> fields) {
|
||||||
|
if (items.isEmpty()) return "";
|
||||||
|
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
|
||||||
|
CSVFormat csvFormat = CSVFormat.DEFAULT.builder()
|
||||||
|
// .setHeader(HEADERS)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (final CSVPrinter printer = new CSVPrinter(sw, csvFormat)) {
|
||||||
|
for (DbObject item : items) {
|
||||||
|
printer.printRecord(fields.stream().map(f -> {
|
||||||
|
return item.get(f).getFormattedValue();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sw.toString();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new DbAdminException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -48,7 +48,7 @@ import tech.ailef.dbadmin.external.exceptions.DbAdminException;
|
|||||||
*/
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping(value = {"/${dbadmin.baseUrl}/download", "/${dbadmin.baseUrl}/download/"})
|
@RequestMapping(value = {"/${dbadmin.baseUrl}/download", "/${dbadmin.baseUrl}/download/"})
|
||||||
public class DownloadController {
|
public class FileDownloadController {
|
||||||
@Autowired
|
@Autowired
|
||||||
private DbAdminRepository repository;
|
private DbAdminRepository repository;
|
||||||
|
|
@ -101,6 +101,12 @@ public class CustomJpaRepository extends SimpleJpaRepository {
|
|||||||
.setFirstResult((page - 1) * pageSize).getResultList();
|
.setFirstResult((page - 1) * pageSize).getResultList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<Object> search(String query, Set<QueryFilter> filters) {
|
||||||
|
return search(query, 1, Integer.MAX_VALUE, null, null, filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public int update(DbObjectSchema schema, Map<String, String> params, Map<String, MultipartFile> files) {
|
public int update(DbObjectSchema schema, Map<String, String> params, Map<String, MultipartFile> files) {
|
||||||
|
@ -87,6 +87,17 @@ public class DbAdminRepository {
|
|||||||
public long count(DbObjectSchema schema, String query, Set<QueryFilter> queryFilters) {
|
public long count(DbObjectSchema schema, String query, Set<QueryFilter> queryFilters) {
|
||||||
return schema.getJpaRepository().count(query, queryFilters);
|
return schema.getJpaRepository().count(query, queryFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DbObject> search(DbObjectSchema schema, String query, Set<QueryFilter> queryFilters) {
|
||||||
|
CustomJpaRepository jpaRepository = schema.getJpaRepository();
|
||||||
|
|
||||||
|
long maxElement = count(schema, query, queryFilters);
|
||||||
|
|
||||||
|
return jpaRepository.search(query, queryFilters).stream()
|
||||||
|
.map(o -> new DbObject(o, schema))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -227,6 +227,16 @@ public class DbField {
|
|||||||
&& getPrimitiveField().getAnnotation(ManyToMany.class) == null;
|
&& getPrimitiveField().getAnnotation(ManyToMany.class) == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this field is exportable into a CSV file.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isExportable() {
|
||||||
|
return !isBinary();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean isGeneratedValue() {
|
public boolean isGeneratedValue() {
|
||||||
return getPrimitiveField().getAnnotation(GeneratedValue.class) != null;
|
return getPrimitiveField().getAnnotation(GeneratedValue.class) != null;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,53 @@
|
|||||||
<head th:replace="~{fragments/resources::head}">
|
<head th:replace="~{fragments/resources::head}">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="modal fade" id="csvExportModal" tabindex="-1" aria-labelledby="csvExportModalLabel" aria-hidden="true">
|
||||||
|
<form th:action="|/${dbadmin_baseUrl}/export/${schema.getClassName()}|" method="GET">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="csvExportModalLabel">Export settings</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<h5 class="fw-bold">Include columns</h5>
|
||||||
|
<div th:each="field : ${schema.getSortedFields()}" th:if="${field.isExportable()}">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox"
|
||||||
|
th:value="${field.getName()}" th:id="|__check_${field.getName()}|"
|
||||||
|
th:name="fields[]"
|
||||||
|
checked>
|
||||||
|
<label class="form-check-label" th:for="|__check_${field.getName()}|">
|
||||||
|
[[ ${field.getName()} ]]
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h5 class="fw-bold mt-3" th:if="${!activeFilters.isEmpty()}">Active filters</h5>
|
||||||
|
<div th:each="filter : ${activeFilters}">
|
||||||
|
<span class="active-filter badge bg-primary me-1 mb-2 p-2 font-monospace noselect">
|
||||||
|
[[ ${filter}]]
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h5 class="fw-bold mt-3">Export format</h3>
|
||||||
|
<select name="format" class="form-select">
|
||||||
|
<option value="csv">CSV</option>
|
||||||
|
<option value="xlsx">XLSX</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Save changes</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- End modal -->
|
||||||
|
|
||||||
|
|
||||||
<div class="bg-light main-wrapper">
|
<div class="bg-light main-wrapper">
|
||||||
<nav th:replace="~{fragments/resources :: navbar}"></nav>
|
<nav th:replace="~{fragments/resources :: navbar}"></nav>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
@ -59,6 +106,9 @@
|
|||||||
<span title="Database table name" class="ms-3 label label-primary label-gray font-monospace">
|
<span title="Database table name" class="ms-3 label label-primary label-gray font-monospace">
|
||||||
[[ ${schema.getTableName()} ]]
|
[[ ${schema.getTableName()} ]]
|
||||||
</span>
|
</span>
|
||||||
|
<button type="button" class="btn p-0 m-0 ms-3" data-bs-toggle="modal" data-bs-target="#csvExportModal">
|
||||||
|
<i class="bi bi-filetype-csv" style="font-size: 1.5rem;"></i>
|
||||||
|
</button>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<h3 class="create-button">
|
<h3 class="create-button">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user