mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-11 12:07:42 +00:00
(improvement) (semantic) support metric data batch download (#358)
Co-authored-by: jolunoluo
This commit is contained in:
@@ -6,9 +6,12 @@ import java.time.LocalDate;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.temporal.TemporalAdjusters;
|
import java.time.temporal.TemporalAdjusters;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import com.tencent.supersonic.common.pojo.Constants;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -126,4 +129,24 @@ public class DateUtils {
|
|||||||
return !timeString.equals("00:00:00");
|
return !timeString.equals("00:00:00");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<String> getDateList(String startDateStr, String endDateStr, String period) {
|
||||||
|
LocalDate startDate = LocalDate.parse(startDateStr);
|
||||||
|
LocalDate endDate = LocalDate.parse(endDateStr);
|
||||||
|
|
||||||
|
List<String> datesInRange = new ArrayList<>();
|
||||||
|
LocalDate currentDate = startDate;
|
||||||
|
|
||||||
|
while (!currentDate.isAfter(endDate)) {
|
||||||
|
datesInRange.add(currentDate.format(DateTimeFormatter.ISO_DATE));
|
||||||
|
if (Constants.MONTH.equals(period)) {
|
||||||
|
currentDate = currentDate.plusMonths(1);
|
||||||
|
} else if (Constants.WEEK.equals(period)) {
|
||||||
|
currentDate = currentDate.plusWeeks(1);
|
||||||
|
} else {
|
||||||
|
currentDate = currentDate.plusDays(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return datesInRange;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package com.tencent.supersonic.common.util;
|
package com.tencent.supersonic.common.util;
|
||||||
|
|
||||||
|
import com.tencent.supersonic.common.pojo.Constants;
|
||||||
|
import org.assertj.core.util.Lists;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
class DateUtilsTest {
|
class DateUtilsTest {
|
||||||
|
|
||||||
@@ -47,4 +51,34 @@ class DateUtilsTest {
|
|||||||
dateStr = DateUtils.getBeforeDate(1, DatePeriodEnum.MONTH);
|
dateStr = DateUtils.getBeforeDate(1, DatePeriodEnum.MONTH);
|
||||||
//Assert.assertEquals(dateStr, "2023-08-08");
|
//Assert.assertEquals(dateStr, "2023-08-08");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDayDateList() {
|
||||||
|
String startDate = "2023-07-29";
|
||||||
|
String endDate = "2023-08-03";
|
||||||
|
List<String> actualDateList = DateUtils.getDateList(startDate, endDate, Constants.DAY);
|
||||||
|
List<String> expectedDateList = Lists.newArrayList("2023-07-29", "2023-07-30",
|
||||||
|
"2023-07-31", "2023-08-01", "2023-08-02", "2023-08-03");
|
||||||
|
Assertions.assertEquals(actualDateList, expectedDateList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWeekDateList() {
|
||||||
|
String startDate = "2023-10-30";
|
||||||
|
String endDate = "2023-11-13";
|
||||||
|
List<String> actualDateList = DateUtils.getDateList(startDate, endDate, Constants.WEEK);
|
||||||
|
List<String> expectedDateList = Lists.newArrayList("2023-10-30", "2023-11-06",
|
||||||
|
"2023-11-13");
|
||||||
|
Assertions.assertEquals(actualDateList, expectedDateList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMonthDateList() {
|
||||||
|
String startDate = "2023-07-01";
|
||||||
|
String endDate = "2023-10-01";
|
||||||
|
List<String> actualDateList = DateUtils.getDateList(startDate, endDate, Constants.MONTH);
|
||||||
|
List<String> expectedDateList = Lists.newArrayList("2023-07-01", "2023-08-01",
|
||||||
|
"2023-09-01", "2023-10-01");
|
||||||
|
Assertions.assertEquals(actualDateList, expectedDateList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,4 +13,7 @@ public class DrillDownDimension {
|
|||||||
|
|
||||||
private boolean necessary;
|
private boolean necessary;
|
||||||
|
|
||||||
|
public DrillDownDimension(Long dimensionId) {
|
||||||
|
this.dimensionId = dimensionId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -62,4 +62,14 @@ public class MetricResp extends SchemaItem {
|
|||||||
return relateDimension.getDrillDownDimensions().stream().filter(DrillDownDimension::isNecessary)
|
return relateDimension.getDrillDownDimensions().stream().filter(DrillDownDimension::isNecessary)
|
||||||
.map(DrillDownDimension::getDimensionId).collect(Collectors.toSet());
|
.map(DrillDownDimension::getDimensionId).collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRelaDimensionIdKey() {
|
||||||
|
if (relateDimension == null || CollectionUtils.isEmpty(relateDimension.getDrillDownDimensions())) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return relateDimension.getDrillDownDimensions().stream()
|
||||||
|
.map(DrillDownDimension::getDimensionId)
|
||||||
|
.map(String::valueOf)
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.tencent.supersonic.semantic.api.query.pojo;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class DataDownload {
|
||||||
|
|
||||||
|
List<List<String>> headers;
|
||||||
|
|
||||||
|
List<List<String>> data;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.tencent.supersonic.semantic.api.query.request;
|
||||||
|
|
||||||
|
import com.tencent.supersonic.common.pojo.DateConf;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class BatchDownloadReq {
|
||||||
|
|
||||||
|
private List<Long> metricIds;
|
||||||
|
|
||||||
|
private DateConf dateInfo;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import com.tencent.supersonic.semantic.api.model.enums.QueryTypeEnum;
|
|||||||
import com.tencent.supersonic.semantic.api.model.response.ExplainResp;
|
import com.tencent.supersonic.semantic.api.model.response.ExplainResp;
|
||||||
import com.tencent.supersonic.semantic.api.model.response.QueryResultWithSchemaResp;
|
import com.tencent.supersonic.semantic.api.model.response.QueryResultWithSchemaResp;
|
||||||
import com.tencent.supersonic.semantic.api.model.response.SqlParserResp;
|
import com.tencent.supersonic.semantic.api.model.response.SqlParserResp;
|
||||||
|
import com.tencent.supersonic.semantic.api.query.request.BatchDownloadReq;
|
||||||
import com.tencent.supersonic.semantic.api.query.request.ExplainSqlReq;
|
import com.tencent.supersonic.semantic.api.query.request.ExplainSqlReq;
|
||||||
import com.tencent.supersonic.semantic.api.query.request.ItemUseReq;
|
import com.tencent.supersonic.semantic.api.query.request.ItemUseReq;
|
||||||
import com.tencent.supersonic.semantic.api.query.request.ParseSqlReq;
|
import com.tencent.supersonic.semantic.api.query.request.ParseSqlReq;
|
||||||
@@ -16,6 +17,7 @@ import com.tencent.supersonic.semantic.api.query.request.QueryMultiStructReq;
|
|||||||
import com.tencent.supersonic.semantic.api.query.request.QueryStructReq;
|
import com.tencent.supersonic.semantic.api.query.request.QueryStructReq;
|
||||||
import com.tencent.supersonic.semantic.api.query.response.ItemUseResp;
|
import com.tencent.supersonic.semantic.api.query.response.ItemUseResp;
|
||||||
import com.tencent.supersonic.semantic.query.persistence.pojo.QueryStatement;
|
import com.tencent.supersonic.semantic.query.persistence.pojo.QueryStatement;
|
||||||
|
import com.tencent.supersonic.semantic.query.service.DownloadService;
|
||||||
import com.tencent.supersonic.semantic.query.service.QueryService;
|
import com.tencent.supersonic.semantic.query.service.QueryService;
|
||||||
import com.tencent.supersonic.semantic.query.service.SemanticQueryEngine;
|
import com.tencent.supersonic.semantic.query.service.SemanticQueryEngine;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -40,6 +42,9 @@ public class QueryController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private SemanticQueryEngine semanticQueryEngine;
|
private SemanticQueryEngine semanticQueryEngine;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DownloadService downloadService;
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("/sql")
|
@PostMapping("/sql")
|
||||||
public Object queryBySql(@RequestBody QueryS2SQLReq queryS2SQLReq,
|
public Object queryBySql(@RequestBody QueryS2SQLReq queryS2SQLReq,
|
||||||
@@ -64,8 +69,15 @@ public class QueryController {
|
|||||||
HttpServletRequest request,
|
HttpServletRequest request,
|
||||||
HttpServletResponse response) throws Exception {
|
HttpServletResponse response) throws Exception {
|
||||||
User user = UserHolder.findUser(request, response);
|
User user = UserHolder.findUser(request, response);
|
||||||
queryService.downloadByStruct(queryStructReq, user, response);
|
downloadService.downloadByStruct(queryStructReq, user, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/download/batch")
|
||||||
|
public void downloadBatch(@RequestBody BatchDownloadReq batchDownloadReq,
|
||||||
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response) throws Exception {
|
||||||
|
User user = UserHolder.findUser(request, response);
|
||||||
|
downloadService.batchDownload(batchDownloadReq, user, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/queryStatement")
|
@PostMapping("/queryStatement")
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.tencent.supersonic.semantic.query.service;
|
||||||
|
|
||||||
|
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||||
|
import com.tencent.supersonic.semantic.api.query.request.BatchDownloadReq;
|
||||||
|
import com.tencent.supersonic.semantic.api.query.request.QueryStructReq;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
public interface DownloadService {
|
||||||
|
|
||||||
|
void downloadByStruct(QueryStructReq queryStructReq,
|
||||||
|
User user, HttpServletResponse response) throws Exception;
|
||||||
|
|
||||||
|
void batchDownload(BatchDownloadReq batchDownloadReq, User user, HttpServletResponse response) throws Exception;
|
||||||
|
}
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
package com.tencent.supersonic.semantic.query.service;
|
||||||
|
|
||||||
|
import com.alibaba.excel.EasyExcel;
|
||||||
|
import com.alibaba.excel.ExcelWriter;
|
||||||
|
import com.alibaba.excel.util.FileUtils;
|
||||||
|
import com.alibaba.excel.write.metadata.WriteSheet;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||||
|
import com.tencent.supersonic.common.pojo.Aggregator;
|
||||||
|
import com.tencent.supersonic.common.pojo.Constants;
|
||||||
|
import com.tencent.supersonic.common.pojo.DateConf;
|
||||||
|
import com.tencent.supersonic.common.pojo.QueryColumn;
|
||||||
|
import com.tencent.supersonic.common.pojo.enums.TimeDimensionEnum;
|
||||||
|
import com.tencent.supersonic.common.util.DateUtils;
|
||||||
|
import com.tencent.supersonic.semantic.api.model.pojo.SchemaItem;
|
||||||
|
import com.tencent.supersonic.semantic.api.model.request.ModelSchemaFilterReq;
|
||||||
|
import com.tencent.supersonic.semantic.api.model.response.DimSchemaResp;
|
||||||
|
import com.tencent.supersonic.semantic.api.model.response.DimensionResp;
|
||||||
|
import com.tencent.supersonic.semantic.api.model.response.MetricResp;
|
||||||
|
import com.tencent.supersonic.semantic.api.model.response.MetricSchemaResp;
|
||||||
|
import com.tencent.supersonic.semantic.api.model.response.ModelSchemaResp;
|
||||||
|
import com.tencent.supersonic.semantic.api.model.response.QueryResultWithSchemaResp;
|
||||||
|
import com.tencent.supersonic.semantic.api.query.pojo.DataDownload;
|
||||||
|
import com.tencent.supersonic.semantic.api.query.request.BatchDownloadReq;
|
||||||
|
import com.tencent.supersonic.semantic.api.query.request.QueryStructReq;
|
||||||
|
import com.tencent.supersonic.semantic.model.domain.ModelService;
|
||||||
|
import com.tencent.supersonic.semantic.query.utils.DataTransformUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class DownloadServiceImpl implements DownloadService {
|
||||||
|
|
||||||
|
private ModelService modelService;
|
||||||
|
|
||||||
|
private QueryService queryService;
|
||||||
|
|
||||||
|
public DownloadServiceImpl(ModelService modelService, QueryService queryService) {
|
||||||
|
this.modelService = modelService;
|
||||||
|
this.queryService = queryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadByStruct(QueryStructReq queryStructReq,
|
||||||
|
User user, HttpServletResponse response) throws Exception {
|
||||||
|
QueryResultWithSchemaResp queryResultWithSchemaResp = queryService.queryByStruct(queryStructReq, user);
|
||||||
|
List<List<String>> data = new ArrayList<>();
|
||||||
|
List<List<String>> header = org.assertj.core.util.Lists.newArrayList();
|
||||||
|
for (QueryColumn column : queryResultWithSchemaResp.getColumns()) {
|
||||||
|
header.add(org.assertj.core.util.Lists.newArrayList(column.getName()));
|
||||||
|
}
|
||||||
|
for (Map<String, Object> row : queryResultWithSchemaResp.getResultList()) {
|
||||||
|
List<String> rowData = new ArrayList<>();
|
||||||
|
for (QueryColumn column : queryResultWithSchemaResp.getColumns()) {
|
||||||
|
rowData.add(String.valueOf(row.get(column.getNameEn())));
|
||||||
|
}
|
||||||
|
data.add(rowData);
|
||||||
|
}
|
||||||
|
String fileName = String.format("%s_%s.xlsx", "supersonic", DateUtils.format(new Date(), DateUtils.FORMAT));
|
||||||
|
File file = FileUtils.createTmpFile(fileName);
|
||||||
|
EasyExcel.write(file).sheet("Sheet1").head(header).doWrite(data);
|
||||||
|
downloadFile(response, file, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadFile(HttpServletResponse response, File file, String filename) {
|
||||||
|
try {
|
||||||
|
byte[] buffer = readFileToByteArray(file);
|
||||||
|
response.reset();
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
|
||||||
|
response.addHeader("Content-Length", "" + file.length());
|
||||||
|
try (OutputStream outputStream = new BufferedOutputStream(response.getOutputStream())) {
|
||||||
|
response.setContentType("application/octet-stream");
|
||||||
|
outputStream.write(buffer);
|
||||||
|
outputStream.flush();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("failed to download file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] readFileToByteArray(File file) throws IOException {
|
||||||
|
try (InputStream fis = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
|
||||||
|
byte[] buffer = new byte[fis.available()];
|
||||||
|
fis.read(buffer);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void batchDownload(BatchDownloadReq batchDownloadReq, User user,
|
||||||
|
HttpServletResponse response) throws Exception {
|
||||||
|
String fileName = String.format("%s_%s.xlsx", "supersonic", DateUtils.format(new Date(), DateUtils.FORMAT));
|
||||||
|
File file = FileUtils.createTmpFile(fileName);
|
||||||
|
List<Long> metricIds = batchDownloadReq.getMetricIds();
|
||||||
|
if (CollectionUtils.isEmpty(metricIds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
batchDownload(batchDownloadReq, user, file);
|
||||||
|
downloadFile(response, file, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void batchDownload(BatchDownloadReq batchDownloadReq, User user, File file) throws Exception {
|
||||||
|
List<Long> metricIds = batchDownloadReq.getMetricIds();
|
||||||
|
List<ModelSchemaResp> modelSchemaRespList = modelService.fetchModelSchema(new ModelSchemaFilterReq());
|
||||||
|
Map<String, List<MetricSchemaResp>> metricSchemaMap = getMetricSchemaMap(modelSchemaRespList, metricIds);
|
||||||
|
Map<Long, DimSchemaResp> dimensionRespMap = getDimensionMap(modelSchemaRespList);
|
||||||
|
ExcelWriter excelWriter = EasyExcel.write(file).build();
|
||||||
|
int sheetCount = 1;
|
||||||
|
for (List<MetricSchemaResp> metrics : metricSchemaMap.values()) {
|
||||||
|
if (CollectionUtils.isEmpty(metrics)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
MetricSchemaResp metricSchemaResp = metrics.get(0);
|
||||||
|
List<DimSchemaResp> dimensions = getMetricRelaDimensions(metricSchemaResp, dimensionRespMap);
|
||||||
|
for (MetricSchemaResp metric : metrics) {
|
||||||
|
DataDownload downloadData = getSingleMetricDownloadData(metric, dimensions,
|
||||||
|
batchDownloadReq.getDateInfo(), user);
|
||||||
|
WriteSheet writeSheet = EasyExcel.writerSheet("Sheet" + sheetCount)
|
||||||
|
.head(downloadData.getHeaders()).build();
|
||||||
|
excelWriter.write(downloadData.getData(), writeSheet);
|
||||||
|
}
|
||||||
|
sheetCount++;
|
||||||
|
}
|
||||||
|
excelWriter.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataDownload getSingleMetricDownloadData(MetricSchemaResp metricSchemaResp, List<DimSchemaResp> dimensions,
|
||||||
|
DateConf dateConf, User user) throws Exception {
|
||||||
|
QueryResultWithSchemaResp queryResult = getQueryResult(dimensions, metricSchemaResp, dateConf, user);
|
||||||
|
List<String> groups = dimensions.stream().map(DimensionResp::getBizName).collect(Collectors.toList());
|
||||||
|
List<String> dateList = getDateList(dateConf);
|
||||||
|
List<Map<String, Object>> dataTransformed = DataTransformUtils.transform(queryResult.getResultList(), dateList,
|
||||||
|
metricSchemaResp.getBizName(), groups);
|
||||||
|
List<List<String>> headers = buildHeader(dimensions, dateList);
|
||||||
|
List<List<String>> data = buildData(headers, getDimensionNameMap(dimensions),
|
||||||
|
dataTransformed, metricSchemaResp);
|
||||||
|
return DataDownload.builder().headers(headers).data(data).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<List<String>> buildHeader(List<DimSchemaResp> dimensionResps, List<String> dateList) {
|
||||||
|
List<List<String>> headers = Lists.newArrayList();
|
||||||
|
for (DimensionResp dimensionResp : dimensionResps) {
|
||||||
|
headers.add(Lists.newArrayList(dimensionResp.getName()));
|
||||||
|
}
|
||||||
|
for (String date : dateList) {
|
||||||
|
headers.add(Lists.newArrayList(date));
|
||||||
|
}
|
||||||
|
headers.add(Lists.newArrayList("指标名"));
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<List<String>> buildData(List<List<String>> headers, Map<String, String> nameMap,
|
||||||
|
List<Map<String, Object>> dataTransformed, MetricSchemaResp metricSchemaResp) {
|
||||||
|
List<List<String>> data = Lists.newArrayList();
|
||||||
|
for (Map<String, Object> map : dataTransformed) {
|
||||||
|
List<String> row = Lists.newArrayList();
|
||||||
|
for (List<String> header : headers) {
|
||||||
|
String head = header.get(0);
|
||||||
|
if ("指标名".equals(head)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
row.add(map.getOrDefault(nameMap.getOrDefault(head, head), "").toString());
|
||||||
|
}
|
||||||
|
row.add(metricSchemaResp.getName());
|
||||||
|
data.add(row);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getDateList(DateConf dateConf) {
|
||||||
|
String startDateStr = dateConf.getStartDate();
|
||||||
|
String endDateStr = dateConf.getEndDate();
|
||||||
|
return DateUtils.getDateList(startDateStr, endDateStr, dateConf.getPeriod());
|
||||||
|
}
|
||||||
|
|
||||||
|
private QueryResultWithSchemaResp getQueryResult(List<DimSchemaResp> dimensionResps, MetricResp metricResp,
|
||||||
|
DateConf dateConf, User user) throws Exception {
|
||||||
|
QueryStructReq queryStructReq = new QueryStructReq();
|
||||||
|
queryStructReq.setGroups(dimensionResps.stream().map(DimSchemaResp::getBizName).collect(Collectors.toList()));
|
||||||
|
queryStructReq.getGroups().add(0, getTimeDimension(dateConf));
|
||||||
|
Aggregator aggregator = new Aggregator();
|
||||||
|
aggregator.setColumn(metricResp.getBizName());
|
||||||
|
queryStructReq.setAggregators(Lists.newArrayList(aggregator));
|
||||||
|
queryStructReq.setDateInfo(dateConf);
|
||||||
|
queryStructReq.setModelId(metricResp.getModelId());
|
||||||
|
return queryService.queryByStruct(queryStructReq, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTimeDimension(DateConf dateConf) {
|
||||||
|
if (Constants.MONTH.equals(dateConf.getPeriod())) {
|
||||||
|
return TimeDimensionEnum.MONTH.getName();
|
||||||
|
} else if (Constants.WEEK.equals(dateConf.getPeriod())) {
|
||||||
|
return TimeDimensionEnum.WEEK.getName();
|
||||||
|
} else {
|
||||||
|
return TimeDimensionEnum.DAY.getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, List<MetricSchemaResp>> getMetricSchemaMap(List<ModelSchemaResp> modelSchemaRespList,
|
||||||
|
List<Long> metricIds) {
|
||||||
|
return modelSchemaRespList.stream().flatMap(modelSchemaResp
|
||||||
|
-> modelSchemaResp.getMetrics().stream())
|
||||||
|
.filter(metricSchemaResp -> metricIds.contains(metricSchemaResp.getId()))
|
||||||
|
.collect(Collectors.groupingBy(MetricSchemaResp::getRelaDimensionIdKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Long, DimSchemaResp> getDimensionMap(List<ModelSchemaResp> modelSchemaRespList) {
|
||||||
|
return modelSchemaRespList.stream().flatMap(modelSchemaResp
|
||||||
|
-> modelSchemaResp.getDimensions().stream())
|
||||||
|
.collect(Collectors.toMap(DimensionResp::getId, dimensionResp -> dimensionResp));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> getDimensionNameMap(List<DimSchemaResp> dimensionResps) {
|
||||||
|
return dimensionResps.stream().collect(Collectors.toMap(DimensionResp::getName, SchemaItem::getBizName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<DimSchemaResp> getMetricRelaDimensions(MetricSchemaResp metricSchemaResp,
|
||||||
|
Map<Long, DimSchemaResp> dimensionRespMap) {
|
||||||
|
return metricSchemaResp.getRelateDimension().getDrillDownDimensions()
|
||||||
|
.stream().map(drillDownDimension -> dimensionRespMap.get(drillDownDimension.getDimensionId()))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -12,8 +12,6 @@ import com.tencent.supersonic.semantic.api.query.request.QueryMultiStructReq;
|
|||||||
import com.tencent.supersonic.semantic.api.query.request.QueryStructReq;
|
import com.tencent.supersonic.semantic.api.query.request.QueryStructReq;
|
||||||
import com.tencent.supersonic.semantic.api.query.response.ItemUseResp;
|
import com.tencent.supersonic.semantic.api.query.response.ItemUseResp;
|
||||||
import com.tencent.supersonic.semantic.query.persistence.pojo.QueryStatement;
|
import com.tencent.supersonic.semantic.query.persistence.pojo.QueryStatement;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface QueryService {
|
public interface QueryService {
|
||||||
@@ -22,8 +20,6 @@ public interface QueryService {
|
|||||||
|
|
||||||
QueryResultWithSchemaResp queryByStruct(QueryStructReq queryStructCmd, User user) throws Exception;
|
QueryResultWithSchemaResp queryByStruct(QueryStructReq queryStructCmd, User user) throws Exception;
|
||||||
|
|
||||||
void downloadByStruct(QueryStructReq queryStructReq, User user, HttpServletResponse response) throws Exception;
|
|
||||||
|
|
||||||
QueryResultWithSchemaResp queryByStructWithAuth(QueryStructReq queryStructCmd, User user)
|
QueryResultWithSchemaResp queryByStructWithAuth(QueryStructReq queryStructCmd, User user)
|
||||||
throws Exception;
|
throws Exception;
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
package com.tencent.supersonic.semantic.query.service;
|
package com.tencent.supersonic.semantic.query.service;
|
||||||
|
|
||||||
import com.alibaba.excel.EasyExcel;
|
|
||||||
import com.alibaba.excel.util.FileUtils;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||||
import com.tencent.supersonic.common.pojo.Aggregator;
|
import com.tencent.supersonic.common.pojo.Aggregator;
|
||||||
import com.tencent.supersonic.common.pojo.DateConf;
|
import com.tencent.supersonic.common.pojo.DateConf;
|
||||||
import com.tencent.supersonic.common.pojo.QueryColumn;
|
|
||||||
import com.tencent.supersonic.common.pojo.enums.TaskStatusEnum;
|
import com.tencent.supersonic.common.pojo.enums.TaskStatusEnum;
|
||||||
import com.tencent.supersonic.common.util.DateUtils;
|
|
||||||
import com.tencent.supersonic.common.util.JsonUtil;
|
import com.tencent.supersonic.common.util.JsonUtil;
|
||||||
import com.tencent.supersonic.common.util.cache.CacheUtils;
|
import com.tencent.supersonic.common.util.cache.CacheUtils;
|
||||||
import com.tencent.supersonic.common.util.ContextUtils;
|
import com.tencent.supersonic.common.util.ContextUtils;
|
||||||
@@ -34,28 +30,16 @@ import com.tencent.supersonic.semantic.query.parser.convert.QueryReqConverter;
|
|||||||
import com.tencent.supersonic.semantic.query.persistence.pojo.QueryStatement;
|
import com.tencent.supersonic.semantic.query.persistence.pojo.QueryStatement;
|
||||||
import com.tencent.supersonic.semantic.query.utils.QueryUtils;
|
import com.tencent.supersonic.semantic.query.utils.QueryUtils;
|
||||||
import com.tencent.supersonic.semantic.query.utils.StatUtils;
|
import com.tencent.supersonic.semantic.query.utils.StatUtils;
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.assertj.core.util.Lists;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -162,52 +146,6 @@ public class QueryServiceImpl implements QueryService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void downloadByStruct(QueryStructReq queryStructReq,
|
|
||||||
User user, HttpServletResponse response) throws Exception {
|
|
||||||
QueryResultWithSchemaResp queryResultWithSchemaResp = queryByStruct(queryStructReq, user);
|
|
||||||
List<List<String>> data = new ArrayList<>();
|
|
||||||
List<List<String>> header = Lists.newArrayList();
|
|
||||||
for (QueryColumn column : queryResultWithSchemaResp.getColumns()) {
|
|
||||||
header.add(Lists.newArrayList(column.getName()));
|
|
||||||
}
|
|
||||||
for (Map<String, Object> row : queryResultWithSchemaResp.getResultList()) {
|
|
||||||
List<String> rowData = new ArrayList<>();
|
|
||||||
for (QueryColumn column : queryResultWithSchemaResp.getColumns()) {
|
|
||||||
rowData.add(String.valueOf(row.get(column.getNameEn())));
|
|
||||||
}
|
|
||||||
data.add(rowData);
|
|
||||||
}
|
|
||||||
String fileName = String.format("%s_%s.xlsx", "supersonic", DateUtils.format(new Date(), DateUtils.FORMAT));
|
|
||||||
File file = FileUtils.createTmpFile(fileName);
|
|
||||||
EasyExcel.write(file).sheet("Sheet1").head(header).doWrite(data);
|
|
||||||
downloadFile(response, file, fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void downloadFile(HttpServletResponse response, File file, String filename) {
|
|
||||||
try {
|
|
||||||
byte[] buffer = readFileToByteArray(file);
|
|
||||||
response.reset();
|
|
||||||
response.setCharacterEncoding("UTF-8");
|
|
||||||
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
|
|
||||||
response.addHeader("Content-Length", "" + file.length());
|
|
||||||
try (OutputStream outputStream = new BufferedOutputStream(response.getOutputStream())) {
|
|
||||||
response.setContentType("application/octet-stream");
|
|
||||||
outputStream.write(buffer);
|
|
||||||
outputStream.flush();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("failed to download file", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] readFileToByteArray(File file) throws IOException {
|
|
||||||
try (InputStream fis = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
|
|
||||||
byte[] buffer = new byte[fis.available()];
|
|
||||||
fis.read(buffer);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@DataPermission
|
@DataPermission
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.tencent.supersonic.semantic.query.utils;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.tencent.supersonic.common.pojo.enums.TimeDimensionEnum;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class DataTransformUtils {
|
||||||
|
|
||||||
|
public static List<Map<String, Object>> transform(List<Map<String, Object>> originalData,
|
||||||
|
List<String> dateList, String metric, List<String> groups) {
|
||||||
|
List<Map<String, Object>> transposedData = new ArrayList<>();
|
||||||
|
for (Map<String, Object> originalRow : originalData) {
|
||||||
|
Map<String, Object> transposedRow = new HashMap<>();
|
||||||
|
for (String key : originalRow.keySet()) {
|
||||||
|
if (groups.contains(key)) {
|
||||||
|
transposedRow.put(key, originalRow.get(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transposedRow.put(String.valueOf(originalRow.get(TimeDimensionEnum.DAY.getName())),
|
||||||
|
originalRow.get(metric));
|
||||||
|
transposedData.add(transposedRow);
|
||||||
|
}
|
||||||
|
Map<String, List<Map<String, Object>>> dataMerge = transposedData.stream()
|
||||||
|
.collect(Collectors.groupingBy(row -> getRowKey(row, groups)));
|
||||||
|
List<Map<String, Object>> resultData = Lists.newArrayList();
|
||||||
|
for (List<Map<String, Object>> data : dataMerge.values()) {
|
||||||
|
Map<String, Object> rowData = new HashMap<>();
|
||||||
|
for (Map<String, Object> row : data) {
|
||||||
|
for (String key : row.keySet()) {
|
||||||
|
rowData.put(key, row.get(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String date : dateList) {
|
||||||
|
if (!rowData.containsKey(date)) {
|
||||||
|
rowData.put(date, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultData.add(rowData);
|
||||||
|
}
|
||||||
|
return resultData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getRowKey(Map<String, Object> originalRow, List<String> groups) {
|
||||||
|
List<Object> values = Lists.newArrayList();
|
||||||
|
for (String key : originalRow.keySet()) {
|
||||||
|
if (groups.contains(key) && !TimeDimensionEnum.getNameList().contains(key)) {
|
||||||
|
values.add(originalRow.get(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StringUtils.join(values, "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package com.tencent.supersonic.semantic.query.service;
|
||||||
|
|
||||||
|
import com.alibaba.excel.util.FileUtils;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||||
|
import com.tencent.supersonic.common.pojo.DateConf;
|
||||||
|
import com.tencent.supersonic.common.util.DateUtils;
|
||||||
|
import com.tencent.supersonic.semantic.api.model.pojo.DrillDownDimension;
|
||||||
|
import com.tencent.supersonic.semantic.api.model.pojo.RelateDimension;
|
||||||
|
import com.tencent.supersonic.semantic.api.model.response.DimSchemaResp;
|
||||||
|
import com.tencent.supersonic.semantic.api.model.response.MetricSchemaResp;
|
||||||
|
import com.tencent.supersonic.semantic.api.model.response.ModelSchemaResp;
|
||||||
|
import com.tencent.supersonic.semantic.api.model.response.QueryResultWithSchemaResp;
|
||||||
|
import com.tencent.supersonic.semantic.api.query.request.BatchDownloadReq;
|
||||||
|
import com.tencent.supersonic.semantic.model.domain.ModelService;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadServiceImplTest {
|
||||||
|
@Test
|
||||||
|
void testBatchDownload() throws Exception {
|
||||||
|
ModelService modelService = Mockito.mock(ModelService.class);
|
||||||
|
QueryService queryService = Mockito.mock(QueryService.class);
|
||||||
|
when(modelService.fetchModelSchema(any())).thenReturn(Lists.newArrayList(mockModelSchemaResp()));
|
||||||
|
when(queryService.queryByStruct(any(), any())).thenReturn(mockQueryResult());
|
||||||
|
DownloadServiceImpl downloadService = new DownloadServiceImpl(modelService, queryService);
|
||||||
|
String fileName = String.format("%s_%s.xlsx", "supersonic", DateUtils.format(new Date(), DateUtils.FORMAT));
|
||||||
|
File file = FileUtils.createTmpFile(fileName);
|
||||||
|
downloadService.batchDownload(buildBatchDownloadReq(), User.getFakeUser(), file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModelSchemaResp mockModelSchemaResp() {
|
||||||
|
ModelSchemaResp modelSchemaResp = new ModelSchemaResp();
|
||||||
|
modelSchemaResp.setId(1L);
|
||||||
|
List<MetricSchemaResp> metricResps = Lists.newArrayList();
|
||||||
|
metricResps.add(mockMetricPv());
|
||||||
|
metricResps.add(mockMetricUv());
|
||||||
|
modelSchemaResp.setMetrics(metricResps);
|
||||||
|
List<DimSchemaResp> dimSchemaResps = Lists.newArrayList();
|
||||||
|
dimSchemaResps.add(mockDimension(1L, "user_name", "用户名"));
|
||||||
|
dimSchemaResps.add(mockDimension(2L, "department", "部门"));
|
||||||
|
dimSchemaResps.add(mockDimension(3L, "page", "页面"));
|
||||||
|
modelSchemaResp.setDimensions(dimSchemaResps);
|
||||||
|
return modelSchemaResp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MetricSchemaResp mockMetric(Long id, String bizName, String name, List<Long> drillDownloadDimensions) {
|
||||||
|
MetricSchemaResp metricResp = new MetricSchemaResp();
|
||||||
|
metricResp.setId(id);
|
||||||
|
metricResp.setBizName(bizName);
|
||||||
|
metricResp.setName(name);
|
||||||
|
RelateDimension relateDimension = new RelateDimension();
|
||||||
|
relateDimension.setDrillDownDimensions(drillDownloadDimensions.stream()
|
||||||
|
.map(DrillDownDimension::new).collect(Collectors.toList()));
|
||||||
|
metricResp.setRelateDimension(relateDimension);
|
||||||
|
return metricResp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DimSchemaResp mockDimension(Long id, String bizName, String name) {
|
||||||
|
DimSchemaResp dimSchemaResp = new DimSchemaResp();
|
||||||
|
dimSchemaResp.setId(id);
|
||||||
|
dimSchemaResp.setBizName(bizName);
|
||||||
|
dimSchemaResp.setName(name);
|
||||||
|
return dimSchemaResp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private MetricSchemaResp mockMetricPv() {
|
||||||
|
return mockMetric(1L, "pv", "访问次数", Lists.newArrayList(1L, 2L));
|
||||||
|
}
|
||||||
|
|
||||||
|
private MetricSchemaResp mockMetricUv() {
|
||||||
|
return mockMetric(2L, "uv", "访问用户数", Lists.newArrayList(2L));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BatchDownloadReq buildBatchDownloadReq() {
|
||||||
|
BatchDownloadReq batchDownloadReq = new BatchDownloadReq();
|
||||||
|
batchDownloadReq.setMetricIds(Lists.newArrayList(1L));
|
||||||
|
batchDownloadReq.setDateInfo(mockDataConf());
|
||||||
|
return batchDownloadReq;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateConf mockDataConf() {
|
||||||
|
DateConf dateConf = new DateConf();
|
||||||
|
dateConf.setStartDate("2023-10-11");
|
||||||
|
dateConf.setEndDate("2023-10-15");
|
||||||
|
dateConf.setDateMode(DateConf.DateMode.BETWEEN);
|
||||||
|
return dateConf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private QueryResultWithSchemaResp mockQueryResult() {
|
||||||
|
QueryResultWithSchemaResp queryResultWithSchemaResp = new QueryResultWithSchemaResp();
|
||||||
|
List<Map<String, Object>> resultList = Lists.newArrayList();
|
||||||
|
resultList.add(createMap("2023-10-11", "tom", "hr", "1"));
|
||||||
|
resultList.add(createMap("2023-10-12", "alice", "sales", "2"));
|
||||||
|
resultList.add(createMap("2023-10-13", "jack", "sales", "3"));
|
||||||
|
resultList.add(createMap("2023-10-14", "luck", "market", "4"));
|
||||||
|
resultList.add(createMap("2023-10-15", "tom", "hr", "5"));
|
||||||
|
queryResultWithSchemaResp.setResultList(resultList);
|
||||||
|
return queryResultWithSchemaResp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> createMap(String sysImpDate, String d1, String d2, String m1) {
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("sys_imp_date", sysImpDate);
|
||||||
|
map.put("user_name", d1);
|
||||||
|
map.put("department", d2);
|
||||||
|
map.put("pv", m1);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.tencent.supersonic.semantic.query.utils;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
class DataTransformUtilsTest {
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransform() {
|
||||||
|
List<Map<String, Object>> inputData = new ArrayList<>();
|
||||||
|
inputData.add(createMap("2023/10/11", "a", "b", "1"));
|
||||||
|
inputData.add(createMap("2023/10/12", "a", "c", "2"));
|
||||||
|
inputData.add(createMap("2023/10/13", "a", "b", "3"));
|
||||||
|
inputData.add(createMap("2023/10/14", "a", "c", "4"));
|
||||||
|
inputData.add(createMap("2023/10/15", "b", "b", "5"));
|
||||||
|
List<String> groups = Lists.newArrayList("d1", "d2");
|
||||||
|
List<String> dateList = Lists.newArrayList("2023/10/11", "2023/10/12",
|
||||||
|
"2023/10/13", "2023/10/14", "2023/10/15");
|
||||||
|
String metric = "m1";
|
||||||
|
List<Map<String, Object>> resultData = DataTransformUtils.transform(inputData, dateList, metric, groups);
|
||||||
|
Assertions.assertEquals(3, resultData.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> createMap(String sysImpDate, String d1, String d2, String m1) {
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("sys_imp_date", sysImpDate);
|
||||||
|
map.put("d1", d1);
|
||||||
|
map.put("d2", d2);
|
||||||
|
map.put("m1", m1);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user