FINERACT-1707 - S3 export and dependent service
- [x] S3 export function - [x] list all available export type services - [x] refactor export service - [x] unit test for export name generator - [x] s3 integration test - [x] localstack integration for testing
This commit is contained in:
committed by
Arnold Gálovics
parent
e08675ed54
commit
d8fd8088b0
@@ -52,12 +52,39 @@ jobs:
|
||||
run: |
|
||||
./gradlew --no-daemon -q createDB -PdbName=fineract_tenants
|
||||
./gradlew --no-daemon -q createDB -PdbName=fineract_default
|
||||
- name: Start LocalStack
|
||||
env:
|
||||
AWS_ENDPOINT_URL: http://localhost:4566
|
||||
AWS_ACCESS_KEY_ID: localstack
|
||||
AWS_SECRET_ACCESS_KEY: localstack
|
||||
AWS_REGION: us-east-1
|
||||
run: |
|
||||
echo "Update python pyopenssl"
|
||||
pip install --upgrade pyopenssl
|
||||
echo "Install localstack"
|
||||
pip install localstack awscli-local[ver1] # install LocalStack cli and awslocal
|
||||
docker pull localstack/localstack # Make sure to pull the latest version of the image
|
||||
localstack start -d # Start LocalStack in the background
|
||||
|
||||
echo "Waiting for LocalStack startup..." # Wait 30 seconds for the LocalStack container
|
||||
localstack wait -t 30 # to become ready before timing out
|
||||
echo "Startup complete"
|
||||
echo "Create fineract S3 bucket"
|
||||
awslocal s3api create-bucket --bucket fineract-reports
|
||||
echo "LocalStack initialization complete"
|
||||
- name: Install additional software
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install ghostscript graphviz -y
|
||||
|
||||
- name: Build & Test
|
||||
env:
|
||||
AWS_ENDPOINT_URL: http://localhost:4566
|
||||
AWS_ACCESS_KEY_ID: localstack
|
||||
AWS_SECRET_ACCESS_KEY: localstack
|
||||
AWS_REGION: us-east-1
|
||||
FINERACT_REPORT_EXPORT_S3_ENABLED: true
|
||||
FINERACT_REPORT_EXPORT_S3_BUCKET_NAME: fineract-reports
|
||||
run: |
|
||||
./gradlew --no-daemon --console=plain build test --fail-fast -x doc -x :twofactor-tests:test -x :oauth2-test:test
|
||||
./gradlew --no-daemon --console=plain :twofactor-tests:test --fail-fast
|
||||
|
||||
@@ -52,12 +52,39 @@ jobs:
|
||||
run: |
|
||||
./gradlew --no-daemon -q createMySQLDB -PdbName=fineract_tenants
|
||||
./gradlew --no-daemon -q createMySQLDB -PdbName=fineract_default
|
||||
- name: Start LocalStack
|
||||
env:
|
||||
AWS_ENDPOINT_URL: http://localhost:4566
|
||||
AWS_ACCESS_KEY_ID: localstack
|
||||
AWS_SECRET_ACCESS_KEY: localstack
|
||||
AWS_REGION: us-east-1
|
||||
run: |
|
||||
echo "Update python pyopenssl"
|
||||
pip install --upgrade pyopenssl
|
||||
echo "Install localstack"
|
||||
pip install localstack awscli-local[ver1] # install LocalStack cli and awslocal
|
||||
docker pull localstack/localstack # Make sure to pull the latest version of the image
|
||||
localstack start -d # Start LocalStack in the background
|
||||
|
||||
echo "Waiting for LocalStack startup..." # Wait 30 seconds for the LocalStack container
|
||||
localstack wait -t 30 # to become ready before timing out
|
||||
echo "Startup complete"
|
||||
echo "Create fineract S3 bucket"
|
||||
awslocal s3api create-bucket --bucket fineract-reports
|
||||
echo "LocalStack initialization complete"
|
||||
- name: Install additional software
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install ghostscript graphviz -y
|
||||
|
||||
- name: Build & Test
|
||||
env:
|
||||
AWS_ENDPOINT_URL: http://localhost:4566
|
||||
AWS_ACCESS_KEY_ID: localstack
|
||||
AWS_SECRET_ACCESS_KEY: localstack
|
||||
AWS_REGION: us-east-1
|
||||
FINERACT_REPORT_EXPORT_S3_ENABLED: true
|
||||
FINERACT_REPORT_EXPORT_S3_BUCKET_NAME: fineract-reports
|
||||
run: |
|
||||
./gradlew --no-daemon --console=plain build test --fail-fast -x doc -x :twofactor-tests:test -x :oauth2-test:test -PdbType=mysql
|
||||
./gradlew --no-daemon --console=plain :twofactor-tests:test --fail-fast -PdbType=mysql
|
||||
|
||||
@@ -53,12 +53,39 @@ jobs:
|
||||
run: |
|
||||
./gradlew --no-daemon -q createPGDB -PdbName=fineract_tenants
|
||||
./gradlew --no-daemon -q createPGDB -PdbName=fineract_default
|
||||
- name: Start LocalStack
|
||||
env:
|
||||
AWS_ENDPOINT_URL: http://localhost:4566
|
||||
AWS_ACCESS_KEY_ID: localstack
|
||||
AWS_SECRET_ACCESS_KEY: localstack
|
||||
AWS_REGION: us-east-1
|
||||
run: |
|
||||
echo "Update python pyopenssl"
|
||||
pip install --upgrade pyopenssl
|
||||
echo "Install localstack"
|
||||
pip install localstack awscli-local[ver1] # install LocalStack cli and awslocal
|
||||
docker pull localstack/localstack # Make sure to pull the latest version of the image
|
||||
localstack start -d # Start LocalStack in the background
|
||||
|
||||
echo "Waiting for LocalStack startup..." # Wait 30 seconds for the LocalStack container
|
||||
localstack wait -t 30 # to become ready before timing out
|
||||
echo "Startup complete"
|
||||
echo "Create fineract S3 bucket"
|
||||
awslocal s3api create-bucket --bucket fineract-reports
|
||||
echo "LocalStack initialization complete"
|
||||
- name: Install additional software
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install ghostscript graphviz -y
|
||||
|
||||
- name: Build & Test
|
||||
env:
|
||||
AWS_ENDPOINT_URL: http://localhost:4566
|
||||
AWS_ACCESS_KEY_ID: localstack
|
||||
AWS_SECRET_ACCESS_KEY: localstack
|
||||
AWS_REGION: us-east-1
|
||||
FINERACT_REPORT_EXPORT_S3_ENABLED: true
|
||||
FINERACT_REPORT_EXPORT_S3_BUCKET_NAME: fineract-reports
|
||||
run: |
|
||||
./gradlew --no-daemon --console=plain build test --fail-fast -x doc -x :twofactor-tests:test -x :oauth2-test:test -PdbType=postgresql
|
||||
./gradlew --no-daemon --console=plain :twofactor-tests:test --fail-fast -PdbType=postgresql
|
||||
|
||||
+2
@@ -134,4 +134,6 @@ public interface ConfigurationDomainService {
|
||||
boolean isCOBBulkEventEnabled();
|
||||
|
||||
Long retrieveExternalEventBatchSize();
|
||||
|
||||
String retrieveReportExportS3FolderName();
|
||||
}
|
||||
|
||||
+7
@@ -50,6 +50,8 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
|
||||
private static final String ENABLE_COB_BULK_EVENT = "enable-cob-bulk-event";
|
||||
private static final String EXTERNAL_EVENT_BATCH_SIZE = "external-event-batch-size";
|
||||
|
||||
private static final String REPORT_EXPORT_S3_FOLDER_NAME = "report-export-s3-folder-name";
|
||||
|
||||
private final PermissionRepository permissionRepository;
|
||||
private final GlobalConfigurationRepositoryWrapper globalConfigurationRepository;
|
||||
private final PlatformCacheRepository cacheTypeRepository;
|
||||
@@ -512,4 +514,9 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
|
||||
return property.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String retrieveReportExportS3FolderName() {
|
||||
final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(REPORT_EXPORT_S3_FOLDER_NAME);
|
||||
return property.getStringValue();
|
||||
}
|
||||
}
|
||||
|
||||
+24
@@ -48,6 +48,8 @@ public class FineractProperties {
|
||||
|
||||
private FineractContentProperties content;
|
||||
|
||||
private FineractReportProperties report;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class FineractTenantProperties {
|
||||
@@ -201,4 +203,26 @@ public class FineractProperties {
|
||||
private String accessKey;
|
||||
private String secretKey;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class FineractReportProperties {
|
||||
|
||||
private FineractExportProperties export;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class FineractExportProperties {
|
||||
|
||||
private FineractExportS3Properties s3;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class FineractExportS3Properties {
|
||||
|
||||
private String bucketName;
|
||||
private Boolean enabled;
|
||||
}
|
||||
}
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.core.service;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class StreamUtil {
|
||||
|
||||
private StreamUtil() {}
|
||||
|
||||
public static <A, B> Collector<A, ?, B> foldLeft(final B init, final BiFunction<? super B, ? super A, ? extends B> f) {
|
||||
return Collectors.collectingAndThen(Collectors.reducing(Function.<B>identity(), a -> b -> f.apply(b, a), Function::andThen),
|
||||
endo -> endo.apply(init));
|
||||
}
|
||||
}
|
||||
+27
-2
@@ -20,6 +20,7 @@ package org.apache.fineract.infrastructure.dataqueries.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
@@ -39,6 +40,7 @@ import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
|
||||
import org.apache.fineract.infrastructure.dataqueries.data.ReportExportType;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
|
||||
import org.apache.fineract.infrastructure.report.provider.ReportingProcessServiceProvider;
|
||||
import org.apache.fineract.infrastructure.report.service.ReportingProcessService;
|
||||
@@ -70,6 +72,29 @@ public class RunreportsApiResource {
|
||||
this.reportingProcessServiceProvider = reportingProcessServiceProvider;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/availableExports/{reportName}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Return all available export types for the specific report", description = "Returns the list of all available export types.")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ReportExportType.class)))) })
|
||||
public Response retrieveAllAvailableExports(@PathParam("reportName") @Parameter(description = "reportName") final String reportName,
|
||||
@Context final UriInfo uriInfo,
|
||||
@DefaultValue("false") @QueryParam(IS_SELF_SERVICE_USER_REPORT_PARAMETER) @Parameter(description = IS_SELF_SERVICE_USER_REPORT_PARAMETER) final boolean isSelfServiceUserReport) {
|
||||
MultivaluedMap<String, String> queryParams = new MultivaluedStringMap();
|
||||
queryParams.putAll(uriInfo.getQueryParameters());
|
||||
|
||||
final boolean parameterType = ApiParameterHelper.parameterType(queryParams);
|
||||
String reportType = readExtraDataAndReportingService.getReportType(reportName, isSelfServiceUserReport, parameterType);
|
||||
ReportingProcessService reportingProcessService = reportingProcessServiceProvider.findReportingProcessService(reportType);
|
||||
if (reportingProcessService == null) {
|
||||
throw new PlatformServiceUnavailableException("err.msg.report.service.implementation.missing",
|
||||
ReportingProcessServiceProvider.SERVICE_MISSING + reportType, reportType);
|
||||
}
|
||||
return Response.ok().entity(reportingProcessService.getAvailableExportTargets()).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{reportName}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@@ -108,8 +133,8 @@ public class RunreportsApiResource {
|
||||
// Pass through isSelfServiceUserReport so that ReportingProcessService implementations can use it
|
||||
queryParams.putSingle(IS_SELF_SERVICE_USER_REPORT_PARAMETER, Boolean.toString(isSelfServiceUserReport));
|
||||
|
||||
String reportType = this.readExtraDataAndReportingService.getReportType(reportName, isSelfServiceUserReport, parameterType);
|
||||
ReportingProcessService reportingProcessService = this.reportingProcessServiceProvider.findReportingProcessService(reportType);
|
||||
String reportType = readExtraDataAndReportingService.getReportType(reportName, isSelfServiceUserReport, parameterType);
|
||||
ReportingProcessService reportingProcessService = reportingProcessServiceProvider.findReportingProcessService(reportType);
|
||||
if (reportingProcessService == null) {
|
||||
throw new PlatformServiceUnavailableException("err.msg.report.service.implementation.missing",
|
||||
ReportingProcessServiceProvider.SERVICE_MISSING + reportType, reportType);
|
||||
|
||||
+1
@@ -38,4 +38,5 @@ final class RunreportsApiResourceSwagger {
|
||||
public List<ResultsetColumnHeaderData> columnHeaders;
|
||||
public List<ResultsetRowData> data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.dataqueries.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Data
|
||||
@RequiredArgsConstructor
|
||||
public class ReportExportType implements Serializable {
|
||||
|
||||
private final String key;
|
||||
private final String queryParameter;
|
||||
}
|
||||
+1
-1
@@ -34,7 +34,7 @@ public enum DatatableExportTargetParameter {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public static DatatableExportTargetParameter checkTarget(final MultivaluedMap<String, String> queryParams) {
|
||||
public static DatatableExportTargetParameter resolverExportTarget(final MultivaluedMap<String, String> queryParams) {
|
||||
for (DatatableExportTargetParameter parameter : DatatableExportTargetParameter.values()) {
|
||||
String parameterName = parameter.getValue();
|
||||
if (queryParams.getFirst(parameterName) != null) {
|
||||
|
||||
+38
-72
@@ -18,105 +18,71 @@
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.dataqueries.service;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import java.util.Optional;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.ResponseBuilder;
|
||||
import javax.ws.rs.core.StreamingOutput;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
|
||||
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.core.service.StreamUtil;
|
||||
import org.apache.fineract.infrastructure.dataqueries.api.RunreportsApiResource;
|
||||
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
|
||||
import org.apache.fineract.infrastructure.dataqueries.data.ReportData;
|
||||
import org.apache.fineract.infrastructure.dataqueries.data.ReportExportType;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.export.DatatableReportExportService;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.export.ResponseHolder;
|
||||
import org.apache.fineract.infrastructure.report.annotation.ReportService;
|
||||
import org.apache.fineract.infrastructure.report.service.ReportingProcessService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@ReportService(type = { "Table", "Chart", "SMS" })
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class DatatableReportingProcessService implements ReportingProcessService {
|
||||
|
||||
private final ReadReportingService readExtraDataAndReportingService;
|
||||
private final ToApiJsonSerializer<ReportData> toApiJsonSerializer;
|
||||
private final GenericDataService genericDataService;
|
||||
|
||||
@Autowired
|
||||
public DatatableReportingProcessService(final ReadReportingService readExtraDataAndReportingService,
|
||||
final GenericDataService genericDataService, final ToApiJsonSerializer<ReportData> toApiJsonSerializer) {
|
||||
this.readExtraDataAndReportingService = readExtraDataAndReportingService;
|
||||
this.toApiJsonSerializer = toApiJsonSerializer;
|
||||
this.genericDataService = genericDataService;
|
||||
}
|
||||
private final List<DatatableReportExportService> exportServices;
|
||||
|
||||
@Override
|
||||
public Response processRequest(String reportName, MultivaluedMap<String, String> queryParams) {
|
||||
boolean isSelfServiceUserReport = Boolean.parseBoolean(
|
||||
queryParams.getOrDefault(RunreportsApiResource.IS_SELF_SERVICE_USER_REPORT_PARAMETER, List.of("false")).get(0));
|
||||
|
||||
DatatableExportTargetParameter exportMode = DatatableExportTargetParameter.checkTarget(queryParams);
|
||||
DatatableExportTargetParameter exportMode = DatatableExportTargetParameter.resolverExportTarget(queryParams);
|
||||
final String parameterTypeValue = ApiParameterHelper.parameterType(queryParams) ? "parameter" : "report";
|
||||
final Map<String, String> reportParams = getReportParams(queryParams);
|
||||
return switch (exportMode) {
|
||||
case CSV -> exportCSV(reportName, queryParams, reportParams, isSelfServiceUserReport, parameterTypeValue);
|
||||
case PDF -> exportPDF(reportName, queryParams, reportParams, isSelfServiceUserReport, parameterTypeValue);
|
||||
case S3 -> exportS3(reportName, queryParams, reportParams, isSelfServiceUserReport, parameterTypeValue);
|
||||
default -> exportJSON(reportName, queryParams, reportParams, isSelfServiceUserReport, parameterTypeValue,
|
||||
exportMode == DatatableExportTargetParameter.PRETTY_JSON);
|
||||
};
|
||||
}
|
||||
|
||||
private Response exportJSON(String reportName, MultivaluedMap<String, String> queryParams, Map<String, String> reportParams,
|
||||
boolean isSelfServiceUserReport, String parameterTypeValue, boolean prettyPrint) {
|
||||
final GenericResultsetData result = this.readExtraDataAndReportingService.retrieveGenericResultset(reportName, parameterTypeValue,
|
||||
reportParams, isSelfServiceUserReport);
|
||||
|
||||
String json;
|
||||
final boolean genericResultSetIsPassed = ApiParameterHelper.genericResultSetPassed(queryParams);
|
||||
final boolean genericResultSet = ApiParameterHelper.genericResultSet(queryParams);
|
||||
if (genericResultSetIsPassed) {
|
||||
if (genericResultSet) {
|
||||
json = this.toApiJsonSerializer.serializePretty(prettyPrint, result);
|
||||
} else {
|
||||
json = this.genericDataService.generateJsonFromGenericResultsetData(result);
|
||||
}
|
||||
} else {
|
||||
json = this.toApiJsonSerializer.serializePretty(prettyPrint, result);
|
||||
ResponseHolder response = findReportExportService(exportMode) //
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unsupported export target: " + exportMode)) //
|
||||
.export(reportName, queryParams, reportParams, isSelfServiceUserReport, parameterTypeValue);
|
||||
Response.ResponseBuilder builder = Response.status(response.status().getStatusCode());
|
||||
if (StringUtils.isNotBlank(response.contentType())) {
|
||||
builder = builder.type(response.contentType());
|
||||
}
|
||||
|
||||
return Response.ok().entity(json).type(MediaType.APPLICATION_JSON).build();
|
||||
if (StringUtils.isNotBlank(response.fileName())) {
|
||||
builder = builder.header("Content-Disposition", "attachment; filename=" + response.fileName());
|
||||
}
|
||||
if (response.entity() != null) {
|
||||
builder = builder.entity(response.entity());
|
||||
}
|
||||
if (response.headers() != null && !response.headers().isEmpty()) {
|
||||
builder = response.headers().stream().collect(StreamUtil.foldLeft(builder, (b, h) -> b.header(h.getKey(), h.getValue())));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private Response exportS3(String reportName, MultivaluedMap<String, String> queryParams, Map<String, String> reportParams,
|
||||
boolean isSelfServiceUserReport, String parameterTypeValue) {
|
||||
throw new UnsupportedOperationException("S3 export not supported for datatables");
|
||||
@Override
|
||||
public List<ReportExportType> getAvailableExportTargets() {
|
||||
return Arrays //
|
||||
.stream(DatatableExportTargetParameter.values()) //
|
||||
.filter(target -> findReportExportService(target).isPresent()) //
|
||||
.map(target -> new ReportExportType(target.name(), target.getValue())) //
|
||||
.toList();
|
||||
}
|
||||
|
||||
private Response exportPDF(String reportName, MultivaluedMap<String, String> queryParams, Map<String, String> reportParams,
|
||||
boolean isSelfServiceUserReport, String parameterTypeValue) {
|
||||
|
||||
final String pdfFileName = this.readExtraDataAndReportingService.retrieveReportPDF(reportName, parameterTypeValue, reportParams,
|
||||
isSelfServiceUserReport);
|
||||
|
||||
final File file = new File(pdfFileName);
|
||||
|
||||
final ResponseBuilder response = Response.ok(file);
|
||||
response.header("Content-Disposition", "attachment; filename=\"" + pdfFileName + "\"");
|
||||
response.header("content-Type", "application/pdf");
|
||||
|
||||
return response.build();
|
||||
private Optional<DatatableReportExportService> findReportExportService(DatatableExportTargetParameter target) {
|
||||
return exportServices.stream().filter(service -> service.supports(target)).findFirst();
|
||||
}
|
||||
|
||||
private Response exportCSV(String reportName, MultivaluedMap<String, String> queryParams, Map<String, String> reportParams,
|
||||
boolean isSelfServiceUserReport, String parameterTypeValue) {
|
||||
final StreamingOutput result = this.readExtraDataAndReportingService.retrieveReportCSV(reportName, parameterTypeValue, reportParams,
|
||||
isSelfServiceUserReport);
|
||||
|
||||
return Response.ok().entity(result).type("text/csv")
|
||||
.header("Content-Disposition", "attachment;filename=" + reportName.replaceAll(" ", "") + ".csv").build();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.dataqueries.service.export;
|
||||
|
||||
import java.util.Map;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.StreamingOutput;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.DatatableExportTargetParameter;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CsvDatatableReportExportServiceImpl implements DatatableReportExportService {
|
||||
|
||||
private final ReadReportingService readExtraDataAndReportingService;
|
||||
|
||||
@Override
|
||||
public ResponseHolder export(String reportName, MultivaluedMap<String, String> queryParams, Map<String, String> reportParams,
|
||||
boolean isSelfServiceUserReport, String parameterTypeValue) {
|
||||
final StreamingOutput result = this.readExtraDataAndReportingService.retrieveReportCSV(reportName, parameterTypeValue, reportParams,
|
||||
isSelfServiceUserReport);
|
||||
return new ResponseHolder(Response.Status.OK).contentType("text/csv")
|
||||
.addHeader("Content-Disposition",
|
||||
"attachment;filename=" + DatatableExportUtil.generatePlainExportFileName(255, "csv", reportName, reportParams))
|
||||
.entity(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(DatatableExportTargetParameter exportType) {
|
||||
return exportType == DatatableExportTargetParameter.CSV;
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.dataqueries.service.export;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class DatatableExportUtil {
|
||||
|
||||
private DatatableExportUtil() {}
|
||||
|
||||
public static String normalizeFolderName(String folderName) {
|
||||
if (StringUtils.isBlank(folderName)) {
|
||||
return "";
|
||||
}
|
||||
String contentNormalizer = folderName.trim() //
|
||||
.replaceAll("[^a-zA-Z0-9!\\-_.'()$/]", "_") // replace special characters
|
||||
.replaceAll("/+", "/") // replace multiply / with a single /
|
||||
.replaceAll("^[./]+", ""); // remove leading . and /
|
||||
|
||||
return contentNormalizer.endsWith("/") ? contentNormalizer.substring(0, contentNormalizer.length() - 1) : contentNormalizer;
|
||||
}
|
||||
|
||||
public static String generatePlainExportFileName(int maxLength, String extension, String reportName, Map<String, String> reportParams) {
|
||||
exportBasicValidation(extension, reportName);
|
||||
return generateReportFileName(maxLength, "", extension, reportName, reportParams);
|
||||
}
|
||||
|
||||
private static void exportBasicValidation(String extension, String reportName) {
|
||||
if (StringUtils.isBlank(extension)) {
|
||||
throw new IllegalArgumentException("The extension is required");
|
||||
}
|
||||
if (StringUtils.isBlank(reportName)) {
|
||||
throw new IllegalArgumentException("The report name is required");
|
||||
}
|
||||
}
|
||||
|
||||
public static String generateS3DatatableExportFileName(int maxLength, String folder, String extension, String reportName,
|
||||
Map<String, String> reportParams) {
|
||||
exportBasicValidation(extension, reportName);
|
||||
if (maxLength < 30) {
|
||||
throw new IllegalArgumentException("The maximum length must be greater than 30");
|
||||
}
|
||||
folder = normalizeFolderName(folder);
|
||||
String reportFinalName = generateReportFileName(maxLength, folder, extension, reportName, reportParams);
|
||||
if (StringUtils.isBlank(folder)) {
|
||||
return reportFinalName;
|
||||
} else {
|
||||
return folder + "/" + reportFinalName;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String generateReportFileName(int maxLength, String folder, String extension, String reportName,
|
||||
Map<String, String> reportParams) {
|
||||
String extensionWithDot = extension.startsWith(".") ? extension : "." + extension;
|
||||
String timestamp = "_" + DateUtils.getOffsetDateTimeOfTenant().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
|
||||
|
||||
int reportMaximumFileName = maxLength - folder.length() - timestamp.length() - extensionWithDot.length() - 1;
|
||||
if (reportMaximumFileName < 0) {
|
||||
throw new IllegalArgumentException("The folder name is too long");
|
||||
}
|
||||
String normalizedFileName = reportName.trim().replaceAll("[^a-zA-Z0-9!\\-_.'()$]", "_");
|
||||
if (reportParams != null) {
|
||||
normalizedFileName += "(" + reportParams.entrySet().stream()
|
||||
.map(entry -> extractReportParameterKey(entry.getKey()) + "_" + entry.getValue()).collect(Collectors.joining(";"))
|
||||
+ ")";
|
||||
}
|
||||
String reportFinalName = normalizedFileName.substring(0, Math.min(normalizedFileName.length(), reportMaximumFileName)) + timestamp
|
||||
+ extensionWithDot;
|
||||
return reportFinalName;
|
||||
}
|
||||
|
||||
private static String extractReportParameterKey(String key) {
|
||||
return key.startsWith("${") && key.endsWith("}") ? key.substring(2, key.length() - 1) : key;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.dataqueries.service.export;
|
||||
|
||||
import java.util.Map;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.DatatableExportTargetParameter;
|
||||
|
||||
public interface DatatableReportExportService {
|
||||
|
||||
ResponseHolder export(String reportName, MultivaluedMap<String, String> queryParams, Map<String, String> reportParams,
|
||||
boolean isSelfServiceUserReport, String parameterTypeValue);
|
||||
|
||||
boolean supports(DatatableExportTargetParameter exportType);
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.dataqueries.service.export;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Header {
|
||||
|
||||
private String key;
|
||||
private String value;
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.dataqueries.service.export;
|
||||
|
||||
import java.util.Map;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
|
||||
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
|
||||
import org.apache.fineract.infrastructure.dataqueries.data.ReportData;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.DatatableExportTargetParameter;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class JsonDatatableReportExportService implements DatatableReportExportService {
|
||||
|
||||
private final ReadReportingService readExtraDataAndReportingService;
|
||||
private final ToApiJsonSerializer<ReportData> toApiJsonSerializer;
|
||||
private final GenericDataService genericDataService;
|
||||
|
||||
@Override
|
||||
public ResponseHolder export(String reportName, MultivaluedMap<String, String> queryParams, Map<String, String> reportParams,
|
||||
boolean isSelfServiceUserReport, String parameterTypeValue) {
|
||||
|
||||
final GenericResultsetData result = this.readExtraDataAndReportingService.retrieveGenericResultset(reportName, parameterTypeValue,
|
||||
reportParams, isSelfServiceUserReport);
|
||||
DatatableExportTargetParameter exportMode = DatatableExportTargetParameter.resolverExportTarget(queryParams);
|
||||
boolean prettyPrint = exportMode == DatatableExportTargetParameter.PRETTY_JSON;
|
||||
String json;
|
||||
final boolean genericResultSetIsPassed = ApiParameterHelper.genericResultSetPassed(queryParams);
|
||||
final boolean genericResultSet = ApiParameterHelper.genericResultSet(queryParams);
|
||||
if (genericResultSetIsPassed) {
|
||||
if (genericResultSet) {
|
||||
json = this.toApiJsonSerializer.serializePretty(prettyPrint, result);
|
||||
} else {
|
||||
json = this.genericDataService.generateJsonFromGenericResultsetData(result);
|
||||
}
|
||||
} else {
|
||||
json = this.toApiJsonSerializer.serializePretty(prettyPrint, result);
|
||||
}
|
||||
return new ResponseHolder(Response.Status.OK).entity(json).contentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(DatatableExportTargetParameter exportType) {
|
||||
return exportType == DatatableExportTargetParameter.JSON || exportType == DatatableExportTargetParameter.PRETTY_JSON;
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.dataqueries.service.export;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.DatatableExportTargetParameter;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PdfDatatableReportExportService implements DatatableReportExportService {
|
||||
|
||||
private final ReadReportingService readExtraDataAndReportingService;
|
||||
|
||||
@Override
|
||||
public ResponseHolder export(String reportName, MultivaluedMap<String, String> queryParams, Map<String, String> reportParams,
|
||||
boolean isSelfServiceUserReport, String parameterTypeValue) {
|
||||
final String pdfFileName = this.readExtraDataAndReportingService.retrieveReportPDF(reportName, parameterTypeValue, reportParams,
|
||||
isSelfServiceUserReport);
|
||||
|
||||
final File file = new File(pdfFileName);
|
||||
|
||||
return new ResponseHolder(Response.Status.OK).contentType("application/pdf")
|
||||
.addHeader("Content-Disposition", "attachment; filename=\"" + pdfFileName + "\"").entity(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(DatatableExportTargetParameter exportType) {
|
||||
return exportType == DatatableExportTargetParameter.PDF;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.dataqueries.service.export;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.ws.rs.core.Response;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Data
|
||||
@Accessors(fluent = true)
|
||||
@RequiredArgsConstructor
|
||||
public class ResponseHolder {
|
||||
|
||||
private String contentType;
|
||||
private String fileName;
|
||||
private final Response.Status status;
|
||||
|
||||
private Object entity;
|
||||
|
||||
private List<Header> headers = new ArrayList<>();
|
||||
|
||||
public ResponseHolder addHeader(String key, String value) {
|
||||
headers.add(new Header(key, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.dataqueries.service.export;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.StreamingOutput;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.DatatableExportTargetParameter;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
|
||||
import software.amazon.awssdk.core.sync.RequestBody;
|
||||
import software.amazon.awssdk.services.s3.S3Client;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class S3DatatableReportExportServiceImpl implements DatatableReportExportService {
|
||||
|
||||
public static final int AWS_S3_MAXIMUM_KEY_LENGTH = 1024;
|
||||
private final ReadReportingService readExtraDataAndReportingService;
|
||||
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final S3Client s3Client;
|
||||
|
||||
private final FineractProperties properties;
|
||||
|
||||
@Override
|
||||
public ResponseHolder export(String reportName, MultivaluedMap<String, String> queryParams, Map<String, String> reportParams,
|
||||
boolean isSelfServiceUserReport, String parameterTypeValue) {
|
||||
try {
|
||||
StreamingOutput output = this.readExtraDataAndReportingService.retrieveReportCSV(reportName, parameterTypeValue, reportParams,
|
||||
isSelfServiceUserReport);
|
||||
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
|
||||
output.write(byteArrayOutputStream);
|
||||
String folder = configurationDomainService.retrieveReportExportS3FolderName();
|
||||
String filePath = DatatableExportUtil.generateS3DatatableExportFileName(AWS_S3_MAXIMUM_KEY_LENGTH, folder, "csv",
|
||||
reportName, reportParams);
|
||||
s3Client.putObject(
|
||||
builder -> builder.bucket(properties.getReport().getExport().getS3().getBucketName()).key(filePath).build(),
|
||||
RequestBody.fromBytes(byteArrayOutputStream.toByteArray()));
|
||||
return new ResponseHolder(Response.Status.NO_CONTENT);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Error while exporting to S3", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(DatatableExportTargetParameter exportType) {
|
||||
return DatatableExportTargetParameter.S3 == exportType;
|
||||
}
|
||||
}
|
||||
+4
@@ -19,16 +19,20 @@
|
||||
package org.apache.fineract.infrastructure.report.service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.apache.fineract.infrastructure.dataqueries.data.ReportExportType;
|
||||
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
|
||||
|
||||
public interface ReportingProcessService {
|
||||
|
||||
Response processRequest(String reportName, MultivaluedMap<String, String> queryParams);
|
||||
|
||||
List<ReportExportType> getAvailableExportTargets();
|
||||
|
||||
default Map<String, String> getReportParams(final MultivaluedMap<String, String> queryParams) {
|
||||
final Map<String, String> reportParams = new HashMap<>();
|
||||
final Set<String> keys = queryParams.keySet();
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.s3;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.export.S3DatatableReportExportServiceImpl;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.providers.AwsRegionProvider;
|
||||
import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;
|
||||
import software.amazon.awssdk.services.s3.S3Client;
|
||||
import software.amazon.awssdk.services.s3.S3ClientBuilder;
|
||||
|
||||
@Configuration
|
||||
@Conditional(AmazonS3ConfigCondition.class)
|
||||
public class AmazonS3Config {
|
||||
|
||||
@Bean
|
||||
public DefaultCredentialsProvider awsCredentialsProvider() {
|
||||
return DefaultCredentialsProvider.create();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AwsRegionProvider awsRegionProvider() {
|
||||
return DefaultAwsRegionProviderChain.builder().build();
|
||||
}
|
||||
|
||||
@Bean("s3Client")
|
||||
public S3Client s3Client(DefaultCredentialsProvider awsCredentialsProvider, AwsRegionProvider awsRegionProvider,
|
||||
List<S3ClientCustomizer> customizers) {
|
||||
S3ClientBuilder builder = S3Client.builder().credentialsProvider(awsCredentialsProvider).region(awsRegionProvider.getRegion());
|
||||
customizers.forEach(customizer -> customizer.customize(builder));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(S3Client.class)
|
||||
public S3DatatableReportExportServiceImpl s3DatatableReportExportServiceImpl(ReadReportingService reportServiceImpl,
|
||||
ConfigurationDomainService configurationDomainService, S3Client s3Client, FineractProperties fineractProperties) {
|
||||
return new S3DatatableReportExportServiceImpl(reportServiceImpl, configurationDomainService, s3Client, fineractProperties);
|
||||
}
|
||||
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.s3;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.condition.PropertiesCondition;
|
||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;
|
||||
|
||||
public class AmazonS3ConfigCondition extends PropertiesCondition {
|
||||
|
||||
@Override
|
||||
protected boolean matches(FineractProperties properties) {
|
||||
FineractProperties.FineractExportS3Properties s3ReportExportProperties = properties.getReport().getExport().getS3();
|
||||
return s3ReportExportProperties.getEnabled() && StringUtils.isNotBlank(s3ReportExportProperties.getBucketName())
|
||||
&& isAwsCredentialValid();
|
||||
}
|
||||
|
||||
private boolean isAwsCredentialValid() {
|
||||
try {
|
||||
DefaultCredentialsProvider.create().resolveCredentials();
|
||||
DefaultAwsRegionProviderChain.builder().build().getRegion();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.s3;
|
||||
|
||||
import java.net.URI;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.poi.util.StringUtil;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Component;
|
||||
import software.amazon.awssdk.services.s3.S3ClientBuilder;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Profile("test")
|
||||
public class LocalstackS3ClientCustomizer implements S3ClientCustomizer {
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
@Override
|
||||
public void customize(S3ClientBuilder builder) {
|
||||
String env = environment.getProperty("AWS_ENDPOINT_URL", "");
|
||||
if (StringUtil.isNotBlank(env)) {
|
||||
builder.endpointOverride(URI.create(env)).forcePathStyle(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.s3;
|
||||
|
||||
import software.amazon.awssdk.services.s3.S3ClientBuilder;
|
||||
|
||||
public interface S3ClientCustomizer {
|
||||
|
||||
void customize(S3ClientBuilder builder);
|
||||
}
|
||||
@@ -86,6 +86,10 @@ fineract.content.s3.bucketName=${FINERACT_CONTENT_S3_BUCKET_NAME:}
|
||||
fineract.content.s3.accessKey=${FINERACT_CONTENT_S3_ACCESS_KEY:}
|
||||
fineract.content.s3.secretKey=${FINERACT_CONTENT_S3_SECRET_KEY:}
|
||||
|
||||
|
||||
fineract.report.export.s3.bucket=${FINERACT_REPORT_EXPORT_S3_BUCKET_NAME:}
|
||||
fineract.report.export.s3.enabled=${FINERACT_REPORT_EXPORT_S3_ENABLED:false}
|
||||
|
||||
# Logging pattern for the console
|
||||
logging.pattern.console=${CONSOLE_LOG_PATTERN:%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(%replace([%X{correlationId}]){'\\[\\]', ''}) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}}
|
||||
|
||||
|
||||
@@ -109,4 +109,5 @@
|
||||
<include file="parts/0087_update_dashboard_table_reports.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0088_drop_m_loan_transaction_version_column.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0089_add_update_loan_arrears_aging_business_step.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0090_add_report_export_s3_folder_configuration.xml" relativeToChangelogFile="true" />
|
||||
</databaseChangeLog>
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
|
||||
<changeSet author="fineract" id="1">
|
||||
<insert tableName="c_configuration">
|
||||
<column name="id" valueNumeric="54"/>
|
||||
<column name="name" value="report-export-s3-folder-name"/>
|
||||
<column name="value"/>
|
||||
<column name="date_value"/>
|
||||
<column name="string_value" value="reports"/>
|
||||
<column name="enabled" valueBoolean="true"/>
|
||||
<column name="is_trap_door" valueBoolean="false"/>
|
||||
<column name="description"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.dataqueries.service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.export.DatatableExportUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class DatatableExportUtilTest {
|
||||
|
||||
@Test
|
||||
public void emptyFolderTest() {
|
||||
Assert.assertEquals("", DatatableExportUtil.normalizeFolderName(""));
|
||||
Assert.assertEquals("", DatatableExportUtil.normalizeFolderName("/"));
|
||||
Assert.assertEquals("", DatatableExportUtil.normalizeFolderName(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void specialCharacterFolderTest() {
|
||||
Assert.assertEquals("_", DatatableExportUtil.normalizeFolderName("Á"));
|
||||
Assert.assertEquals("_", DatatableExportUtil.normalizeFolderName("="));
|
||||
Assert.assertEquals("_", DatatableExportUtil.normalizeFolderName("\\"));
|
||||
Assert.assertEquals("_", DatatableExportUtil.normalizeFolderName("@"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizedFolderNameTest() {
|
||||
Assert.assertEquals("$", DatatableExportUtil.normalizeFolderName("$"));
|
||||
Assert.assertEquals("reports", DatatableExportUtil.normalizeFolderName("reports"));
|
||||
Assert.assertEquals("reports", DatatableExportUtil.normalizeFolderName("reports/"));
|
||||
Assert.assertEquals("reports", DatatableExportUtil.normalizeFolderName("/reports/"));
|
||||
Assert.assertEquals("reports/content", DatatableExportUtil.normalizeFolderName("reports/content"));
|
||||
Assert.assertEquals("reports/content", DatatableExportUtil.normalizeFolderName("reports/////content"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateDatatableExportFileNameSuccessTest() {
|
||||
String reportName = "reportName";
|
||||
Map<String, String> reportParams = Collections.synchronizedSortedMap(new TreeMap<>(Map.of("param1", "value1", "param2", "value2")));
|
||||
String fileName = DatatableExportUtil.generateS3DatatableExportFileName(1024, "folder", "csv", reportName, reportParams);
|
||||
Assert.assertTrue(fileName.matches("folder/reportName\\(param1_value1;param2_value2\\)_\\d{14}.csv"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateDatatableExportFileNameComplexTest() {
|
||||
String reportName = "reportName";
|
||||
Map<String, String> reportParams = Collections.synchronizedSortedMap(new TreeMap<>(Map.of("param1", "value1", "param2", "value2")));
|
||||
Assert.assertTrue(DatatableExportUtil.generateS3DatatableExportFileName(1024, "folder///name///", "csv", reportName, reportParams)
|
||||
.matches("folder/name/reportName\\(param1_value1;param2_value2\\)_\\d{14}.csv"));
|
||||
IllegalArgumentException folderTooLongException = Assert.assertThrows(IllegalArgumentException.class, () -> {
|
||||
DatatableExportUtil.generateS3DatatableExportFileName(30, "too_long_folder_name_test", "csv", reportName, reportParams);
|
||||
});
|
||||
Assert.assertEquals("The folder name is too long", folderTooLongException.getMessage());
|
||||
|
||||
IllegalArgumentException maximumLengthException = Assert.assertThrows(IllegalArgumentException.class, () -> {
|
||||
DatatableExportUtil.generateS3DatatableExportFileName(29, "folder///name/", "csv", reportName, reportParams);
|
||||
});
|
||||
Assert.assertEquals("The maximum length must be greater than 30", maximumLengthException.getMessage());
|
||||
|
||||
IllegalArgumentException extensionRequired = Assert.assertThrows(IllegalArgumentException.class, () -> {
|
||||
DatatableExportUtil.generateS3DatatableExportFileName(30, "too_long_folder_name_test", null, reportName, reportParams);
|
||||
});
|
||||
Assert.assertEquals("The extension is required", extensionRequired.getMessage());
|
||||
|
||||
IllegalArgumentException reportNameRequired = Assert.assertThrows(IllegalArgumentException.class, () -> {
|
||||
DatatableExportUtil.generateS3DatatableExportFileName(30, "too_long_folder_name_test", "csv", null, reportParams);
|
||||
});
|
||||
Assert.assertEquals("The report name is required", reportNameRequired.getMessage());
|
||||
|
||||
Assert.assertTrue(DatatableExportUtil.generateS3DatatableExportFileName(1024, "folder///name/", ".csv", reportName, null)
|
||||
.matches("folder/name/reportName_\\d{14}.csv"));
|
||||
|
||||
Assert.assertTrue(
|
||||
DatatableExportUtil.generateS3DatatableExportFileName(1024, "folder///name/", "csv", "report/with/slash", reportParams)
|
||||
.matches("folder/name/report_with_slash\\(param1_value1;param2_value2\\)_\\d{14}.csv"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -70,6 +70,8 @@ fineract.content.s3.enabled=false
|
||||
fineract.content.s3.bucketName=
|
||||
fineract.content.s3.accessKey=
|
||||
fineract.content.s3.secretKey=
|
||||
fineract.report.export.s3.bucket=${FINERACT_REPORT_EXPORT_S3_BUCKET_NAME:}
|
||||
fineract.report.export.s3.enabled=${FINERACT_REPORT_EXPORT_S3_ENABLED:false}
|
||||
|
||||
management.health.jms.enabled=false
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.integrationtests;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@EnabledIfEnvironmentVariable(named = "GITHUB_ACTIONS", matches = "true")
|
||||
public @interface CIOnly {}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.integrationtests.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.ResponseBody;
|
||||
import org.apache.fineract.integrationtests.CIOnly;
|
||||
import org.apache.fineract.integrationtests.common.Utils;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import retrofit2.Response;
|
||||
|
||||
/**
|
||||
* Integration Test for /runreports/ API.
|
||||
*
|
||||
* @author Michael Vorburger.ch
|
||||
*/
|
||||
public class ReportExportTest extends IntegrationTest {
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
Utils.initializeRESTAssured();
|
||||
}
|
||||
|
||||
@Test
|
||||
void runClientListingTableReportCSV() throws IOException {
|
||||
Response<ResponseBody> result = okR(
|
||||
fineract().reportsRun.runReportGetFile("Client Listing", Map.of("R_officeId", "1", "exportCSV", "true"), false));
|
||||
assertThat(result.body().contentType()).isEqualTo(MediaType.parse("text/csv"));
|
||||
assertThat(result.body().string()).contains("Office/Branch");
|
||||
}
|
||||
|
||||
@Test
|
||||
@CIOnly
|
||||
void runClientListingTableReportS3() throws IOException {
|
||||
Response<ResponseBody> result = okR(
|
||||
fineract().reportsRun.runReportGetFile("Client Listing", Map.of("R_officeId", "1", "exportS3", "true"), false));
|
||||
assertThat(result.code()).isEqualTo(204);
|
||||
}
|
||||
|
||||
}
|
||||
+9
@@ -27,6 +27,7 @@ import org.apache.fineract.integrationtests.common.Utils;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import retrofit2.Response;
|
||||
|
||||
/**
|
||||
* Integration Test for /runreports/ API.
|
||||
@@ -51,6 +52,14 @@ public class ReportsTest extends IntegrationTest {
|
||||
.getColumnName()).isEqualTo("Office/Branch");
|
||||
}
|
||||
|
||||
@Test
|
||||
void runClientListingTableReportCSV() throws IOException {
|
||||
Response<ResponseBody> result = okR(
|
||||
fineract().reportsRun.runReportGetFile("Client Listing", Map.of("R_officeId", "1", "exportCSV", "true"), false));
|
||||
assertThat(result.body().contentType()).isEqualTo(MediaType.parse("text/csv"));
|
||||
assertThat(result.body().string()).contains("Office/Branch");
|
||||
}
|
||||
|
||||
@Test // see FINERACT-1306
|
||||
void runReportCategory() throws IOException {
|
||||
// Using raw OkHttp instead of Retrofit API here, because /runreports/reportCategoryList returns JSON Array -
|
||||
|
||||
+13
-5
@@ -96,9 +96,9 @@ public class GlobalConfigurationHelper {
|
||||
// If any other column is modified by the integration test suite in
|
||||
// the future, it needs to be reset here.
|
||||
final Integer configDefaultId = (Integer) defaultGlobalConfiguration.get("id");
|
||||
final Integer configDefaultValue = (Integer) defaultGlobalConfiguration.get("value");
|
||||
final String configDefaultValue = String.valueOf(defaultGlobalConfiguration.get("value"));
|
||||
|
||||
updateValueForGlobalConfiguration(requestSpec, responseSpec, configDefaultId.toString(), configDefaultValue.toString());
|
||||
updateValueForGlobalConfiguration(requestSpec, responseSpec, configDefaultId.toString(), configDefaultValue);
|
||||
updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, configDefaultId.toString(),
|
||||
(Boolean) defaultGlobalConfiguration.get("enabled"));
|
||||
changedNo++;
|
||||
@@ -119,9 +119,9 @@ public class GlobalConfigurationHelper {
|
||||
ArrayList<HashMap> expectedGlobalConfigurations = getAllDefaultGlobalConfigurations();
|
||||
ArrayList<HashMap> actualGlobalConfigurations = getAllGlobalConfigurations(requestSpec, responseSpec);
|
||||
|
||||
// There are currently 48 global configurations.
|
||||
Assertions.assertEquals(48, expectedGlobalConfigurations.size());
|
||||
Assertions.assertEquals(48, actualGlobalConfigurations.size());
|
||||
// There are currently 49 global configurations.
|
||||
Assertions.assertEquals(49, expectedGlobalConfigurations.size());
|
||||
Assertions.assertEquals(49, actualGlobalConfigurations.size());
|
||||
|
||||
for (int i = 0; i < expectedGlobalConfigurations.size(); i++) {
|
||||
|
||||
@@ -540,6 +540,14 @@ public class GlobalConfigurationHelper {
|
||||
externalEventBatchSize.put("enabled", false);
|
||||
externalEventBatchSize.put("trapDoor", false);
|
||||
defaults.add(externalEventBatchSize);
|
||||
|
||||
HashMap<String, Object> reportExportS3FolderName = new HashMap<>();
|
||||
reportExportS3FolderName.put("id", 54);
|
||||
reportExportS3FolderName.put("name", "report-export-s3-folder-name");
|
||||
reportExportS3FolderName.put("value", 0);
|
||||
reportExportS3FolderName.put("enabled", false);
|
||||
reportExportS3FolderName.put("trapDoor", false);
|
||||
defaults.add(reportExportS3FolderName);
|
||||
return defaults;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user