Merge branch 'dev-v2' of github.com:dataease/dataease into dev-v2

This commit is contained in:
wangjiahao 2024-09-25 22:49:41 +08:00
commit 83b0a8be0e
56 changed files with 1401 additions and 1375 deletions

View File

@ -250,6 +250,120 @@ public class DefaultChartHandler extends AbstractChartPlugin {
return res;
}
protected List<ChartViewFieldDTO> getAssistFields(List<ChartSeniorAssistDTO> list, List<ChartViewFieldDTO> yAxis, List<ChartViewFieldDTO> xAxis) {
List<ChartViewFieldDTO> res = new ArrayList<>();
for (ChartSeniorAssistDTO dto : list) {
DatasetTableFieldDTO curField = dto.getCurField();
ChartViewFieldDTO field = null;
String alias = "";
for (int i = 0; i < yAxis.size(); i++) {
ChartViewFieldDTO yField = yAxis.get(i);
if (Objects.equals(yField.getId(), curField.getId())) {
field = yField;
alias = String.format(SQLConstants.FIELD_ALIAS_Y_PREFIX, i);
break;
}
}
if (ObjectUtils.isEmpty(field) && CollectionUtils.isNotEmpty(xAxis)) {
for (int i = 0; i < xAxis.size(); i++) {
ChartViewFieldDTO xField = xAxis.get(i);
if (StringUtils.equalsIgnoreCase(String.valueOf(xField.getId()), String.valueOf(curField.getId()))) {
field = xField;
alias = String.format(SQLConstants.FIELD_ALIAS_X_PREFIX, i);
break;
}
}
}
if (ObjectUtils.isEmpty(field)) {
continue;
}
ChartViewFieldDTO chartViewFieldDTO = new ChartViewFieldDTO();
BeanUtils.copyBean(chartViewFieldDTO, curField);
chartViewFieldDTO.setSummary(dto.getSummary());
chartViewFieldDTO.setOriginName(alias);// yAxis的字段别名就是查找的字段名
res.add(chartViewFieldDTO);
}
return res;
}
public List<ChartSeniorAssistDTO> getDynamicThresholdFields(ChartViewDTO view) {
List<ChartSeniorAssistDTO> list = new ArrayList<>();
Map<String, Object> senior = view.getSenior();
if (ObjectUtils.isEmpty(senior)) {
return list;
}
ChartSeniorThresholdCfgDTO thresholdCfg = JsonUtil.parseObject((String) JsonUtil.toJSONString(senior.get("threshold")), ChartSeniorThresholdCfgDTO.class);
if (null == thresholdCfg || !thresholdCfg.isEnable()) {
return list;
}
List<TableThresholdDTO> tableThreshold = thresholdCfg.getTableThreshold();
if (ObjectUtils.isEmpty(tableThreshold)) {
return list;
}
List<ChartSeniorThresholdDTO> conditionsList = tableThreshold.stream()
.filter(item -> !ObjectUtils.isEmpty(item))
.map(TableThresholdDTO::getConditions)
.flatMap(List::stream)
.filter(condition -> StringUtils.equalsAnyIgnoreCase(condition.getType(), "dynamic"))
.toList();
List<ChartSeniorAssistDTO> assistDTOs = conditionsList.stream()
.flatMap(condition -> getConditionFields(condition).stream())
.filter(this::solveThresholdCondition)
.toList();
list.addAll(assistDTOs);
return list;
}
private boolean solveThresholdCondition(ChartSeniorAssistDTO fieldDTO) {
Long fieldId = fieldDTO.getFieldId();
String summary = fieldDTO.getValue();
if (ObjectUtils.isEmpty(fieldId) || StringUtils.isEmpty(summary)) {
return false;
}
DatasetTableFieldDTO datasetTableFieldDTO = datasetTableFieldManage.selectById(fieldId);
if (ObjectUtils.isEmpty(datasetTableFieldDTO)) {
return false;
}
ChartViewFieldDTO datasetTableField = new ChartViewFieldDTO();
BeanUtils.copyBean(datasetTableField, datasetTableFieldDTO);
fieldDTO.setCurField(datasetTableField);
fieldDTO.setSummary(summary);
return true;
}
private List<ChartSeniorAssistDTO> getConditionFields(ChartSeniorThresholdDTO condition) {
List<ChartSeniorAssistDTO> list = new ArrayList<>();
if ("between".equals(condition.getTerm())) {
if (!StringUtils.equalsIgnoreCase(condition.getDynamicMaxField().getSummary(), "value")) {
list.add(of(condition.getDynamicMaxField()));
}
if (!StringUtils.equalsIgnoreCase(condition.getDynamicMinField().getSummary(), "value")) {
list.add(of(condition.getDynamicMinField()));
}
} else {
if (!StringUtils.equalsIgnoreCase(condition.getDynamicField().getSummary(), "value")) {
list.add(of(condition.getDynamicField()));
}
}
return list;
}
private ChartSeniorAssistDTO of(ThresholdDynamicFieldDTO dynamicField){
ChartSeniorAssistDTO conditionField = new ChartSeniorAssistDTO();
conditionField.setFieldId(Long.parseLong(dynamicField.getFieldId()));
conditionField.setValue(dynamicField.getSummary());
return conditionField;
}
protected String assistSQL(String sql, List<ChartViewFieldDTO> assistFields) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < assistFields.size(); i++) {

View File

@ -120,11 +120,11 @@ public class YoyChartHandler extends DefaultChartHandler {
expandedResult.setQuerySql(originSql);
}
// 同环比数据排序
expandedResult.setOriginData(sortData(view, expandedResult.getOriginData()));
expandedResult.setOriginData(sortData(view, expandedResult.getOriginData(),formatResult));
return expandedResult;
}
public static List<String[]> sortData(ChartViewDTO view, List<String[]> data) {
public static List<String[]> sortData(ChartViewDTO view, List<String[]> data, AxisFormatResult formatResult) {
// 维度排序
List<ChartViewFieldDTO> xAxisSortList = view.getXAxis().stream().filter(x -> !StringUtils.equalsIgnoreCase("none", x.getSort())).toList();
// 指标排序
@ -135,11 +135,9 @@ public class YoyChartHandler extends DefaultChartHandler {
ChartViewFieldDTO firstYAxis = yAxisSortList.getFirst();
boolean asc = firstYAxis.getSort().equalsIgnoreCase("asc");
// 维度指标
List<ChartViewFieldDTO> allAxisList = Stream.of(
view.getXAxis(),
view.getXAxisExt(),
view.getYAxis()
).flatMap(List::stream).toList();
List<ChartViewFieldDTO> allAxisList = new ArrayList<>();
allAxisList.addAll(formatResult.getAxisMap().get(ChartAxis.xAxis));
allAxisList.addAll(formatResult.getAxisMap().get(ChartAxis.yAxis));
int index = findIndex(allAxisList, firstYAxis.getId());
return sortData(data, asc, index);
}

View File

@ -12,8 +12,9 @@ import io.dataease.extensions.datasource.provider.Provider;
import io.dataease.extensions.view.dto.*;
import io.dataease.extensions.view.util.ChartDataUtil;
import io.dataease.extensions.view.util.FieldUtil;
import io.dataease.utils.JsonUtil;
import io.dataease.utils.BeanUtils;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
@ -21,6 +22,8 @@ import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@Component
public class TableInfoHandler extends DefaultChartHandler {
@ -135,6 +138,32 @@ public class TableInfoHandler extends DefaultChartHandler {
calcResult.setContext(filterResult.getContext());
calcResult.setQuerySql(querySql);
calcResult.setOriginData(data);
try {
var dynamicAssistFields = getDynamicThresholdFields(view);
Set<Long> fieldIds = xAxis.stream().map(ChartViewFieldDTO::getId).collect(Collectors.toSet());
List<ChartViewFieldDTO> finalXAxis = xAxis;
dynamicAssistFields.forEach(i -> {
if (!fieldIds.contains(i.getFieldId())) {
ChartViewFieldDTO fieldDTO = new ChartViewFieldDTO();
BeanUtils.copyBean(fieldDTO, i.getCurField());
finalXAxis.add(fieldDTO);
}
});
var yAxis = formatResult.getAxisMap().get(ChartAxis.yAxis);
var assistFields = getAssistFields(dynamicAssistFields, yAxis, xAxis);
if (CollectionUtils.isNotEmpty(assistFields)) {
var req = new DatasourceRequest();
req.setDsList(dsMap);
var assistSql = assistSQL(querySql, assistFields);
req.setQuery(assistSql);
logger.debug("calcite assistSql sql: " + assistSql);
var assistData = (List<String[]>) provider.fetchResultField(req).get("data");
calcResult.setAssistData(assistData);
calcResult.setDynamicAssistFields(dynamicAssistFields);
}
} catch (Exception e) {
e.printStackTrace();
}
return calcResult;
}
}

View File

@ -1,9 +1,18 @@
package io.dataease.chart.charts.impl.table;
import io.dataease.chart.charts.impl.YoyChartHandler;
import io.dataease.extensions.datasource.dto.DatasourceRequest;
import io.dataease.extensions.datasource.dto.DatasourceSchemaDTO;
import io.dataease.extensions.datasource.model.SQLMeta;
import io.dataease.extensions.datasource.provider.Provider;
import io.dataease.extensions.view.dto.*;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
* @author jianneng
* @date 2024/9/11 11:37
@ -12,4 +21,29 @@ import org.springframework.stereotype.Component;
public class TableNormalHandler extends YoyChartHandler {
@Getter
private String type = "table-normal";
@Override
public <T extends ChartCalcDataResult> T calcChartResult(ChartViewDTO view, AxisFormatResult formatResult, CustomFilterResult filterResult, Map<String, Object> sqlMap, SQLMeta sqlMeta, Provider provider) {
var dsMap = (Map<Long, DatasourceSchemaDTO>) sqlMap.get("dsMap");
var result = (T) super.calcChartResult(view, formatResult, filterResult, sqlMap, sqlMeta, provider);
try {
var originSql = result.getQuerySql();
var dynamicAssistFields = getDynamicThresholdFields(view);
var yAxis = formatResult.getAxisMap().get(ChartAxis.yAxis);
var assistFields = getAssistFields(dynamicAssistFields, yAxis);
if (CollectionUtils.isNotEmpty(assistFields)) {
var req = new DatasourceRequest();
req.setDsList(dsMap);
var assistSql = assistSQL(originSql, assistFields);
req.setQuery(assistSql);
logger.debug("calcite assistSql sql: " + assistSql);
var assistData = (List<String[]>) provider.fetchResultField(req).get("data");
result.setAssistData(assistData);
result.setDynamicAssistFields(dynamicAssistFields);
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}

View File

@ -17,6 +17,7 @@ import io.dataease.utils.BeanUtils;
import io.dataease.utils.IDUtils;
import io.dataease.utils.JsonUtil;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import reactor.util.function.Tuple2;
@ -35,6 +36,25 @@ public class TablePivotHandler extends GroupChartHandler {
T result = super.calcChartResult(view, formatResult, filterResult, sqlMap, sqlMeta, provider);
Map<String, Object> customCalc = calcCustomExpr(view, filterResult, sqlMap, sqlMeta, provider);
result.getData().put("customCalc", customCalc);
try {
var dsMap = (Map<Long, DatasourceSchemaDTO>) sqlMap.get("dsMap");
var originSql = result.getQuerySql();
var dynamicAssistFields = getDynamicThresholdFields(view);
var yAxis = formatResult.getAxisMap().get(ChartAxis.yAxis);
var assistFields = getAssistFields(dynamicAssistFields, yAxis);
if (CollectionUtils.isNotEmpty(assistFields)) {
var req = new DatasourceRequest();
req.setDsList(dsMap);
var assistSql = assistSQL(originSql, assistFields);
req.setQuery(assistSql);
logger.debug("calcite assistSql sql: " + assistSql);
var assistData = (List<String[]>) provider.fetchResultField(req).get("data");
result.setAssistData(assistData);
result.setDynamicAssistFields(dynamicAssistFields);
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}

View File

@ -110,7 +110,6 @@ public class ChartDataManage {
}
var dillAxis = new ArrayList<ChartViewFieldDTO>();
DatasetGroupInfoDTO table = datasetGroupManage.getDatasetGroupInfoDTO(view.getTableId(), null);
if (table == null) {
DEException.throwException(ResultCode.DATA_IS_WRONG.code(), Translator.get("i18n_no_ds"));

View File

@ -6,4 +6,5 @@ package io.dataease.dataset.constant;
public class DatasetTableType {
public static String DB = "db";
public static String SQL = "sql";
public static String Es = "es";
}

View File

@ -129,6 +129,15 @@ public class DatasetDataManage {
datasourceRequest.setTable(tableInfoDTO.getTable());
}
tableFields = provider.fetchTableField(datasourceRequest);
} else if (StringUtils.equalsIgnoreCase(type, DatasetTableType.Es)) {
CoreDatasource coreDatasource = coreDatasourceMapper.selectById(datasetTableDTO.getDatasourceId());
Provider provider = ProviderFactory.getProvider(type);
DatasourceRequest datasourceRequest = new DatasourceRequest();
DatasourceSchemaDTO datasourceSchemaDTO = new DatasourceSchemaDTO();
BeanUtils.copyBean(datasourceSchemaDTO, coreDatasource);
datasourceRequest.setDatasource(datasourceSchemaDTO);
datasourceRequest.setTable(datasetTableDTO.getTableName());
tableFields = provider.fetchTableField(datasourceRequest);
} else {
// excel,api
@ -185,9 +194,7 @@ public class DatasetDataManage {
DEException.throwException(Translator.get("i18n_no_column_permission"));
}
}
buildFieldName(sqlMap, fields);
Map<Long, DatasourceSchemaDTO> dsMap = (Map<Long, DatasourceSchemaDTO>) sqlMap.get("dsMap");
DatasourceUtils.checkDsStatus(dsMap);
List<String> dsList = new ArrayList<>();
@ -202,13 +209,11 @@ public class DatasetDataManage {
}
sql = Utils.replaceSchemaAlias(sql, dsMap);
}
List<DataSetRowPermissionsTreeDTO> rowPermissionsTree = new ArrayList<>();
TokenUserBO user = AuthUtils.getUser();
if (user != null && checkPermission) {
rowPermissionsTree = permissionManage.getRowPermissionsTree(datasetGroupInfoDTO.getId(), user.getUserId());
}
Provider provider;
if (crossDs) {
provider = ProviderFactory.getDefaultProvider();
@ -236,7 +241,6 @@ public class DatasetDataManage {
DatasourceRequest datasourceRequest = new DatasourceRequest();
datasourceRequest.setQuery(querySQL);
datasourceRequest.setDsList(dsMap);
Map<String, Object> data = provider.fetchResultField(datasourceRequest);
Map<String, Object> map = new LinkedHashMap<>();

View File

@ -494,7 +494,16 @@ public class DatasetSQLManage {
datasourceSchemaDTO.setSchemaAlias(schemaAlias);
dsMap.put(coreDatasource.getId(), datasourceSchemaDTO);
}
} else {
} else if (StringUtils.equalsIgnoreCase(ds.getType(), DatasetTableType.Es)){
CoreDatasource coreDatasource = coreDatasourceMapper.selectById(ds.getDatasourceId());
schemaAlias = String.format(SQLConstants.SCHEMA, coreDatasource.getId());
if (!dsMap.containsKey(coreDatasource.getId())) {
DatasourceSchemaDTO datasourceSchemaDTO = new DatasourceSchemaDTO();
BeanUtils.copyBean(datasourceSchemaDTO, coreDatasource);
datasourceSchemaDTO.setSchemaAlias(schemaAlias);
dsMap.put(coreDatasource.getId(), datasourceSchemaDTO);
}
}else {
CoreDatasource coreDatasource = engineManage.getDeEngine();
schemaAlias = String.format(SQLConstants.SCHEMA, coreDatasource.getId());
if (!dsMap.containsKey(coreDatasource.getId())) {

View File

@ -0,0 +1,29 @@
package io.dataease.datasource.dto.es;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class EsResponse {
private List<Column> columns = new ArrayList<>();
private List<String[]> rows = new ArrayList<>();
private String cursor;
private Integer status;
private Error error;
private String version;
@Data
public class Error {
private String type;
private String reason;
}
@Data
public class Column {
private String name;
private String type;
}
}

View File

@ -0,0 +1,10 @@
package io.dataease.datasource.dto.es;
import lombok.Data;
@Data
public class Request {
private String query;
private Integer fetch_size = 10000;
private boolean field_multi_value_leniency = true;
}

View File

@ -0,0 +1,8 @@
package io.dataease.datasource.dto.es;
import lombok.Data;
@Data
public class RequestWithCursor extends Request {
private String cursor;
}

View File

@ -1271,7 +1271,6 @@ public class CalciteProvider extends Provider {
try {
connection = initConnection(dsMap);
} catch (Exception e) {
e.printStackTrace();
}
});

View File

@ -0,0 +1,211 @@
package io.dataease.datasource.provider;
import com.google.gson.Gson;
import com.google.gson.JsonParser;
import io.dataease.dataset.utils.FieldUtils;
import io.dataease.datasource.dto.es.EsResponse;
import io.dataease.datasource.dto.es.Request;
import io.dataease.datasource.type.Es;
import io.dataease.exception.DEException;
import io.dataease.extensions.datasource.dto.*;
import io.dataease.extensions.datasource.provider.Provider;
import io.dataease.i18n.Translator;
import io.dataease.utils.HttpClientConfig;
import io.dataease.utils.HttpClientUtil;
import io.dataease.utils.JsonUtil;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHeaders;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
@Service("esProvider")
public class EsProvider extends Provider {
@Override
public List<String> getSchema(DatasourceRequest datasourceRequest) {
return new ArrayList<>();
}
@Override
public List<DatasetTableDTO> getTables(DatasourceRequest datasourceRequest) {
List<DatasetTableDTO> tables = new ArrayList<>();
try {
String response = execQuery(datasourceRequest, "show tables", "?format=json");
tables = fetchTables(response);
tables = tables.stream().filter(table -> StringUtils.isNotEmpty(table.getTableName()) && !table.getTableName().startsWith(".")).collect(Collectors.toList());
tables.forEach(table -> {
table.setDatasourceId(datasourceRequest.getDatasource().getId());
});
} catch (Exception e) {
e.getMessage();
DEException.throwException(e);
}
return tables;
}
@Override
public ConnectionObj getConnection(DatasourceDTO coreDatasource) throws Exception {
return null;
}
@Override
public String checkStatus(DatasourceRequest datasourceRequest) throws Exception {
Es es = JsonUtil.parseObject(datasourceRequest.getDatasource().getConfiguration(), Es.class);
String response = execGetQuery(datasourceRequest);
if (JsonParser.parseString(response).getAsJsonObject().getAsJsonObject("error") != null) {
throw new Exception(JsonParser.parseString(response).getAsJsonObject().getAsJsonObject("error").get("reason").getAsString());
}
String version = JsonParser.parseString(response).getAsJsonObject().getAsJsonObject("version").get("number").getAsString();
String[] versionList = version.split("\\.");
if (Integer.valueOf(versionList[0]) < 7 && Integer.valueOf(versionList[1]) < 3) {
throw new Exception(Translator.get("i18n_es_limit"));
}
if (Integer.valueOf(versionList[0]) == 6) {
es.setUri("_xpack/sql");
}
if (Integer.valueOf(versionList[0]) > 6) {
es.setUri("_sql");
}
datasourceRequest.getDatasource().setConfiguration(JsonUtil.toJSONString(es).toString());
getTables(datasourceRequest);
return "Success";
}
@Override
public Map<String, Object> fetchResultField(DatasourceRequest datasourceRequest) {
Map<String, Object> result = new HashMap<>();
try {
String response = execQuery(datasourceRequest, datasourceRequest.getQuery(), "?format=json");
result.put("dataList", fetchResultData(response));
result.put("fieldList", fetchResultField4Sql(response));
} catch (Exception e) {
e.printStackTrace();
DEException.throwException(e);
}
return result;
}
@Override
public List<TableField> fetchTableField(DatasourceRequest datasourceRequest) {
List<TableField> tableFields = new ArrayList<>();
try {
String response = execQuery(datasourceRequest, "select * from " + datasourceRequest.getTable() + " limit 0", "?format=json");
tableFields = fetchResultField4Sql(response);
} catch (Exception e) {
DEException.throwException(e);
}
return tableFields;
}
@Override
public void hidePW(DatasourceDTO datasourceDTO) {
}
private List<String[]> fetchResultData(String response) throws Exception {
EsResponse esResponse = new Gson().fromJson(response, EsResponse.class);
return fetchResultData(esResponse);
}
private List<String[]> fetchResultData(EsResponse esResponse) throws Exception {
List<String[]> list = new LinkedList<>();
if (esResponse.getError() != null) {
throw new Exception(esResponse.getError().getReason());
}
list.addAll(esResponse.getRows());
return list;
}
private List<TableField> fetchResultField4Sql(String response) throws Exception {
List<TableField> fieldList = new ArrayList<>();
EsResponse esResponse = new Gson().fromJson(response, EsResponse.class);
if (esResponse.getError() != null) {
throw new Exception(esResponse.getError().getReason());
}
for (EsResponse.Column column : esResponse.getColumns()) {
TableField field = new TableField();
field.setOriginName(column.getName());
field.setOriginName(column.getName());
field.setFieldType(column.getType());
field.setType(column.getType().toUpperCase());
field.setFieldType(field.getType());
int deType = FieldUtils.transType2DeType(field.getType());
field.setDeExtractType(deType);
field.setDeType(deType);
fieldList.add(field);
}
return fieldList;
}
private List<DatasetTableDTO> fetchTables(String response) throws Exception {
List<DatasetTableDTO> tables = new ArrayList<>();
EsResponse esResponse = new Gson().fromJson(response, EsResponse.class);
if (esResponse.getError() != null) {
throw new Exception(esResponse.getError().getReason());
}
for (String[] row : esResponse.getRows()) {
DatasetTableDTO tableDesc = new DatasetTableDTO();
if (row.length == 3 && row[1].contains("TABLE") && row[2].equalsIgnoreCase("INDEX")) {
tableDesc.setTableName(row[0]);
}
if (row.length == 2 && row[1].contains("TABLE")) {
tableDesc.setTableName(row[0]);
}
if (row.length == 4 && row[2].contains("TABLE") && row[3].equalsIgnoreCase("INDEX")) {
tableDesc.setTableName(row[1]);
}
tableDesc.setType("es");
tables.add(tableDesc);
}
return tables;
}
private String execQuery(DatasourceRequest datasourceRequest, String sql, String uri) {
Es es = null;
if (datasourceRequest.getDatasource() == null) {
Collection<DatasourceSchemaDTO> datasourceSchemaDTOS = datasourceRequest.getDsList().values();
es = JsonUtil.parseObject(datasourceSchemaDTOS.stream().findFirst().get().getConfiguration(), Es.class);
} else {
es = JsonUtil.parseObject(datasourceRequest.getDatasource().getConfiguration(), Es.class);
}
uri = es.getUri() + uri;
HttpClientConfig httpClientConfig = new HttpClientConfig();
if (StringUtils.isNotEmpty(es.getUsername()) && StringUtils.isNotEmpty(es.getPassword())) {
String auth = es.getUsername() + ":" + es.getPassword();
byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.UTF_8));
httpClientConfig.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + new String(encodedAuth));
}
Request request = new Request();
request.setQuery(sql);
request.setFetch_size(datasourceRequest.getFetchSize());
String url = es.getUrl().endsWith("/") ? es.getUrl() + uri : es.getUrl() + "/" + uri;
return HttpClientUtil.post(url, new Gson().toJson(request), httpClientConfig);
}
private String execGetQuery(DatasourceRequest datasourceRequest) {
Es es = JsonUtil.parseObject(datasourceRequest.getDatasource().getConfiguration(), Es.class);
HttpClientConfig httpClientConfig = new HttpClientConfig();
if (StringUtils.isNotEmpty(es.getUsername()) && StringUtils.isNotEmpty(es.getPassword())) {
String auth = es.getUsername() + ":" + es.getPassword();
byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.UTF_8));
httpClientConfig.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + new String(encodedAuth));
}
return HttpClientUtil.get(es.getUrl(), httpClientConfig);
}
}

View File

@ -0,0 +1,15 @@
package io.dataease.datasource.type;
import lombok.Data;
import org.springframework.stereotype.Component;
@Data
public class Es {
private String url;
private String username;
private String password;
private String version;
private String uri;
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 742 KiB

After

Width:  |  Height:  |  Size: 61 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 739 KiB

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 742 KiB

After

Width:  |  Height:  |  Size: 61 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 741 KiB

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 739 KiB

After

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 742 KiB

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 744 KiB

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 739 KiB

After

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 744 KiB

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 739 KiB

After

Width:  |  Height:  |  Size: 147 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 739 KiB

After

Width:  |  Height:  |  Size: 97 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 739 KiB

After

Width:  |  Height:  |  Size: 147 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 742 KiB

After

Width:  |  Height:  |  Size: 63 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 739 KiB

After

Width:  |  Height:  |  Size: 65 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 742 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -13,6 +13,7 @@ import redshiftDs from '@/assets/svg/redshift-ds.svg'
import APIDs from '@/assets/svg/API-ds.svg'
import ExcelDs from '@/assets/svg/Excel-ds.svg'
import dorisDs from '@/assets/svg/doris-ds.svg'
import esDs from '@/assets/svg/es-ds.svg'
const iconDatasourceMap = {
mysql: mysqlDs,
oracle: oracleDs,
@ -28,7 +29,8 @@ const iconDatasourceMap = {
redshift: redshiftDs,
API: APIDs,
Excel: ExcelDs,
doris: dorisDs
doris: dorisDs,
es: esDs
}
export { iconDatasourceMap }

View File

@ -111,6 +111,27 @@ const btnStyle = computed(() => {
return style
})
const btnPlainStyle = computed(() => {
const style = {
backgroundColor: 'transparent',
borderColor: customStyle.btnColor,
color: customStyle.btnColor
} as CSSProperties
if (customStyle.fontSizeBtn) {
style.fontSize = customStyle.fontSizeBtn + 'px'
}
if (customStyle.fontWeightBtn) {
style.fontWeight = customStyle.fontWeightBtn
}
if (customStyle.fontStyleBtn) {
style.fontStyle = customStyle.fontStyleBtn
}
return style
})
const curComponentView = computed(() => {
return (canvasViewInfo.value[element.value.id] || {}).customStyle
})
@ -619,10 +640,20 @@ const autoStyle = computed(() => {
</div>
</div>
<div class="query-button" v-if="!!listVisible.length">
<el-button @click.stop="clearData" v-if="customStyle.btnList.includes('clear')" secondary>
<el-button
@click.stop="clearData"
:style="btnPlainStyle"
v-if="customStyle.btnList.includes('clear')"
plain
>
{{ t('commons.clear') }}
</el-button>
<el-button @click.stop="resetData" v-if="customStyle.btnList.includes('reset')" secondary>
<el-button
@click.stop="resetData"
:style="btnPlainStyle"
v-if="customStyle.btnList.includes('reset')"
plain
>
{{ t('chart.reset') }}
</el-button>
<el-button

View File

@ -365,7 +365,11 @@ export default {
please_insert_end: 'End Time Column Name',
save_form: 'Save Form',
default: 'default',
default_built_in: 'Built-in Database'
default_built_in: 'Built-in Database',
lt_check: 'need less than {0}',
gt_check: 'need greater than {0}',
le_check: 'need less than or equal to {0}',
ge_check: 'need greater than or equal to {0}'
},
database: {
nvarchar: 'Nvarchar',

View File

@ -266,7 +266,11 @@ export default {
please_insert_end: '請輸入結束時間',
save_form: '保存表單',
default: '默認',
default_built_in: '內建數據庫'
default_built_in: '內建數據庫',
lt_check: '值需要小于{0}',
gt_check: '值需要大于{0}',
le_check: '值需要小于等于{0}',
ge_check: '值需要大于等于{0}'
},
database: {
nvarchar: '字符串',

View File

@ -2703,7 +2703,11 @@ export default {
please_insert_end: '请输入结束时间',
save_form: '保存表单',
default: '默认',
default_built_in: '内建数据库'
default_built_in: '内建数据库',
lt_check: '值需要小于{0}',
gt_check: '值需要大于{0}',
le_check: '值需要小于等于{0}',
ge_check: '值需要大于等于{0}'
},
database: {
nvarchar: '字符串',

View File

@ -198,6 +198,28 @@ declare interface Threshold {
* url
*/
url: string
/**
* 类型固定值动态值
*/
type: 'fixed' | 'dynamic'
/**
* 动态值字段
*/
dynamicField: ThresholdDynamicField
/**
* 动态值最小值字段 仅当term为between时使用
*/
dynamicMinField: ThresholdDynamicField
/**
* 动态值最大值字段 仅当term为between时使用
*/
dynamicMaxField: ThresholdDynamicField
}
declare interface ThresholdDynamicField {
fieldId: string
summary: string
field: ChartViewField
}
/**

View File

@ -193,40 +193,62 @@ const changeTableThreshold = () => {
ElMessage.error(t('chart.exp_can_not_empty'))
return
}
if (ele.term === 'between') {
if (
!ele.term.includes('null') &&
!ele.term.includes('empty') &&
(ele.min === '' || ele.max === '')
) {
ElMessage.error(t('chart.value_can_not_empty'))
return
}
if (
(field.field.deType === 2 || field.field.deType === 3 || field.field.deType === 4) &&
(parseFloat(ele.min).toString() === 'NaN' || parseFloat(ele.max).toString() === 'NaN')
) {
ElMessage.error(t('chart.value_error'))
return
}
if (
(field.field.deType === 2 || field.field.deType === 3 || field.field.deType === 4) &&
parseFloat(ele.min) > parseFloat(ele.max)
) {
ElMessage.error(t('chart.value_min_max_invalid'))
return
if (ele.type !== 'dynamic') {
if (ele.term === 'between') {
if (
!ele.term.includes('null') &&
!ele.term.includes('empty') &&
(ele.min === '' || ele.max === '')
) {
ElMessage.error(t('chart.value_can_not_empty'))
return
}
if (
(field.field.deType === 2 || field.field.deType === 3 || field.field.deType === 4) &&
(parseFloat(ele.min).toString() === 'NaN' || parseFloat(ele.max).toString() === 'NaN')
) {
ElMessage.error(t('chart.value_error'))
return
}
if (
(field.field.deType === 2 || field.field.deType === 3 || field.field.deType === 4) &&
parseFloat(ele.min) > parseFloat(ele.max)
) {
ElMessage.error(t('chart.value_min_max_invalid'))
return
}
} else {
if (!ele.term.includes('null') && !ele.term.includes('empty') && ele.value === '') {
ElMessage.error(t('chart.value_can_not_empty'))
return
}
if (
(field.field.deType === 2 || field.field.deType === 3 || field.field.deType === 4) &&
parseFloat(ele.value).toString() === 'NaN'
) {
ElMessage.error(t('chart.value_error'))
return
}
}
} else {
if (!ele.term.includes('null') && !ele.term.includes('empty') && ele.value === '') {
ElMessage.error(t('chart.value_can_not_empty'))
return
}
if (
(field.field.deType === 2 || field.field.deType === 3 || field.field.deType === 4) &&
parseFloat(ele.value).toString() === 'NaN'
) {
ElMessage.error(t('chart.value_error'))
return
if (ele.term === 'between') {
if (
!ele.term.includes('null') &&
!ele.term.includes('empty') &&
(!ele.dynamicMinField?.fieldId || !ele.dynamicMaxField?.fieldId)
) {
ElMessage.error(t('chart.field_can_not_empty'))
return
}
} else {
if (
!ele.term.includes('null') &&
!ele.term.includes('empty') &&
!ele.dynamicField?.fieldId
) {
ElMessage.error(t('chart.field_can_not_empty'))
return
}
}
}
}
@ -235,7 +257,24 @@ const changeTableThreshold = () => {
changeThreshold()
closeTableThreshold()
}
const getFieldName = field => (field.chartShowName ? field.chartShowName : field.name)
const getDynamicStyleLabel = (item, fieldObj) => {
const handleSummary = field => {
if (!field?.field) {
return ''
}
if (field.summary === 'value') {
return getFieldName(field.field) + '(' + t('chart.field') + ')'
} else {
let suffix = field.summary === 'avg' ? t('chart.drag_block_label_value') : ''
return getFieldName(field.field) + '(' + t('chart.' + field.summary) + suffix + ')'
}
}
if (item.type === 'dynamic') {
return handleSummary(fieldObj)
}
}
init()
</script>
@ -519,17 +558,15 @@ init()
style="flex-direction: column"
>
<div class="field-style" :class="{ 'field-style-dark': themes === 'dark' }">
<span>
<el-icon>
<Icon :className="`field-icon-${fieldType[fieldItem.field.deType]}`"
><component
class="svg-icon"
:class="`field-icon-${fieldType[fieldItem.field.deType]}`"
:is="iconFieldMap[fieldType[fieldItem.field.deType]]"
></component
></Icon>
</el-icon>
</span>
<el-icon>
<Icon :className="`field-icon-${fieldType[fieldItem.field.deType]}`"
><component
class="svg-icon"
:class="`field-icon-${fieldType[fieldItem.field.deType]}`"
:is="iconFieldMap[fieldType[fieldItem.field.deType]]"
></component
></Icon>
</el-icon>
<span :title="fieldItem.field.name" class="field-text">{{
fieldItem.field.name
}}</span>
@ -577,7 +614,17 @@ init()
</span>
<span v-else-if="item.term === 'default'" title="默认"> 默认 </span>
</div>
<div style="flex: 1; margin: 0 8px">
<div v-if="item.type !== 'dynamic'" style="flex: 1; margin: 0 8px">
<span style="margin: 0 8px">
{{ t('chart.fix') }}
</span>
</div>
<div v-else style="flex: 1; margin: 0 8px">
<span style="margin: 0 8px">
{{ t('chart.dynamic') }}
</span>
</div>
<div v-if="item.type !== 'dynamic'" style="flex: 1; margin: 0 8px">
<span
v-if="
!item.term.includes('null') &&
@ -594,11 +641,44 @@ init()
!item.term.includes('empty') &&
item.term === 'between'
"
:title="item.min + ' ≤= ' + t('chart.drag_block_label_value') + ' ≤ ' + item.max"
>
{{ item.min }}&nbsp;{{ t('chart.drag_block_label_value') }}&nbsp;{{ item.max }}
</span>
<span v-else>&nbsp;</span>
</div>
<div v-else style="flex: 1; margin: 0 8px">
<span
v-if="
!item.term.includes('null') &&
!item.term.includes('default') &&
!item.term.includes('empty') &&
item.term !== 'between'
"
:title="getDynamicStyleLabel(item, item.dynamicField) + ''"
>
{{ getDynamicStyleLabel(item, item.dynamicField) }}</span
>
<span
v-else-if="
!item.term.includes('null') &&
!item.term.includes('empty') &&
item.term === 'between'
"
:title="
getDynamicStyleLabel(item, item.dynamicMinField) +
'≤' +
t('chart.drag_block_label_value') +
'≤' +
getDynamicStyleLabel(item, item.dynamicMaxField)
"
>
{{ getDynamicStyleLabel(item, item.dynamicMinField) }}{{
t('chart.drag_block_label_value')
}}{{ getDynamicStyleLabel(item, item.dynamicMaxField) }}
</span>
<span v-else>&nbsp;</span>
</div>
<template v-if="chart.type === 'picture-group'">
<div title="显示图片" class="pic-group-main">
<img
@ -688,7 +768,7 @@ init()
v-model="state.editTableThresholdDialog"
:title="t('chart.threshold')"
:visible="state.editTableThresholdDialog"
width="800px"
width="1050px"
class="dialog-css"
append-to-body
>
@ -826,12 +906,21 @@ span {
flex-direction: row;
align-items: center;
flex-wrap: nowrap;
:nth-child(1) {
width: 48px !important;
}
:nth-child(2) {
width: 40px !important;
}
:nth-child(3) {
width: 30px !important;
}
&:deep(span) {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
cursor: default;
display: block;
}
}
@ -868,9 +957,7 @@ span {
display: flex;
align-items: center;
justify-content: flex-start;
background: #f5f6f7;
&.field-style-dark {
background: #1a1a1a;
}

View File

@ -30,7 +30,11 @@ const thresholdCondition = {
color: '#ff0000ff',
backgroundColor: '#ffffff00',
min: '0',
max: '1'
max: '1',
type: 'fixed',
dynamicField: { summary: 'value' },
dynamicMinField: { summary: 'value' },
dynamicMaxField: { summary: 'value' }
}
const textOptions = [
{
@ -241,11 +245,108 @@ const addField = item => {
item.field = JSON.parse(JSON.stringify(ele))
initOptions(item)
}
if (item.dynamicField?.fieldId === ele.id) {
item.dynamicField.field = JSON.parse(JSON.stringify(ele))
initOptions(item)
}
if (item.dynamicMinField?.fieldId === ele.id) {
item.dynamicMinField.field = JSON.parse(JSON.stringify(ele))
initOptions(item)
}
if (item.dynamicMaxField?.fieldId === ele.id) {
item.dynamicMaxField.field = JSON.parse(JSON.stringify(ele))
initOptions(item)
}
})
}
changeThreshold()
}
const fieldOptions = [
{ label: t('chart.field_fixed'), value: 'fixed' },
{ label: t('chart.field_dynamic'), value: 'dynamic' }
]
const dynamicSummaryOptions = [
{
id: 'value',
name: t('chart.field') + t('chart.drag_block_label_value')
},
{
id: 'avg',
name: t('chart.avg') + t('chart.drag_block_label_value')
},
{
id: 'max',
name: t('chart.max')
},
{
id: 'min',
name: t('chart.min')
}
]
const getConditionsFields = (fieldItem, conditionItem, conditionItemField) => {
const fieldItemDeType = state.fields.filter(ele => ele.id === fieldItem.fieldId)?.[0]?.deType
if (!fieldItemDeType) {
fieldItem.fieldId = null
conditionItem.fieldId = null
conditionItemField.fieldId = null
changeThreshold()
return state.fields
}
const result = state.fields.filter(item => item.deType === fieldItemDeType) ?? []
if (!result.find(ele => ele.id === conditionItemField.fieldId)) {
conditionItemField.fieldId = result[0]?.id
addField(conditionItem)
}
return result
}
const getDynamicSummaryOptions = itemId => {
const deType = state.fields.filter(ele => ele.id === itemId)?.[0]?.deType
if (deType === 1) {
//
return dynamicSummaryOptions.filter(ele => {
return ele.id !== 'avg'
})
} else if (deType === 0 || deType === 5) {
//
return dynamicSummaryOptions.filter(ele => {
return ele.id === 'value'
})
} else {
return dynamicSummaryOptions
}
}
const isNotEmptyAndNull = item => {
return !item.term.includes('null') && !item.term.includes('empty')
}
const isBetween = item => {
return item.term === 'between'
}
const isDynamic = item => {
return item.type === 'dynamic'
}
const changeConditionItemType = item => {
if (item.type === 'dynamic') {
item.dynamicField.summary = 'value'
item.dynamicMinField.summary = 'value'
item.dynamicMaxField.summary = 'value'
}
changeThreshold()
}
const getFieldOptions = fieldItem => {
const deType = state.fields.filter(ele => ele.id === fieldItem.fieldId)?.[0]?.deType
if (deType === 1) {
return fieldOptions.filter(ele => ele.value === 'fixed')
} else {
return fieldOptions
}
}
init()
</script>
@ -310,9 +411,9 @@ init()
v-for="(item, index) in fieldItem.conditions"
:key="index"
class="line-item"
:gutter="10"
:gutter="12"
>
<el-col :span="4">
<el-col :span="3">
<el-form-item class="form-item">
<el-select v-model="item.term" @change="changeThreshold">
<el-option-group
@ -330,13 +431,27 @@ init()
</el-select>
</el-form-item>
</el-col>
<el-col :span="2" v-if="isNotEmptyAndNull(item)" style="padding-left: 0 !important">
<el-form-item class="form-item">
<el-select
v-model="item.type"
class="select-item"
@change="changeConditionItemType(item)"
style="width: 100%"
>
<el-option
v-for="opt in getFieldOptions(fieldItem)"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
</el-col>
<!--不是between 不是动态值-->
<el-col
v-if="
!item.term.includes('null') &&
!item.term.includes('empty') &&
item.term !== 'between'
"
:span="10"
v-if="isNotEmptyAndNull(item) && !isBetween(item) && !isDynamic(item)"
:span="12"
style="text-align: center"
>
<el-form-item class="form-item">
@ -359,8 +474,72 @@ init()
/>
</el-form-item>
</el-col>
<el-col v-if="item.term === 'between'" :span="4" style="text-align: center">
<!--不是between 是动态值-->
<!--动态值 字段-->
<el-col v-if="isNotEmptyAndNull(item) && !isBetween(item) && isDynamic(item)" :span="6">
<el-form-item class="form-item">
<el-select
v-model="item.dynamicField.fieldId"
@change="addField(item)"
style="width: 100%"
>
<el-option
class="series-select-option"
v-for="itemFieldOption in getConditionsFields(
fieldItem,
item,
item.dynamicField
)"
:key="itemFieldOption.id"
:label="itemFieldOption.name"
:value="itemFieldOption.id"
:disabled="chart.type === 'table-info' && itemFieldOption.deType === 7"
>
<el-icon style="margin-right: 8px">
<Icon
><component
:class="`field-icon-${
fieldType[[2, 3].includes(itemFieldOption.deType) ? 2 : 0]
}`"
class="svg-icon"
:is="iconFieldMap[fieldType[itemFieldOption.deType]]"
></component
></Icon>
</el-icon>
{{ itemFieldOption.name }}
</el-option>
</el-select>
</el-form-item>
</el-col>
<!--动态值聚合方式-->
<el-col
v-if="isNotEmptyAndNull(item) && !isBetween(item) && isDynamic(item)"
:span="6"
style="text-align: center"
>
<el-form-item class="form-item">
<el-select
:placeholder="t('chart.aggregation')"
v-model="item.dynamicField.summary"
@change="changeThreshold"
style="width: 100%"
>
<el-option
v-for="opt in getDynamicSummaryOptions(item.dynamicField.fieldId)"
:key="opt.id"
:label="opt.name"
:value="opt.id"
/>
</el-select>
</el-form-item>
</el-col>
<!--是between 不是动态值-->
<!--between 开始值-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && !isDynamic(item)"
:span="5"
style="text-align: center"
>
<el-form-item class="form-item">
<el-input-number
v-model="item.min"
@ -372,14 +551,21 @@ init()
/>
</el-form-item>
</el-col>
<el-col v-if="item.term === 'between'" :span="2" style="text-align: center">
<span style="margin: 0 4px">
&nbsp;&nbsp;{{ t('chart.drag_block_label_value') }}&nbsp;&nbsp;
<el-col
v-if="isBetween(item) && !isDynamic(item)"
:span="2"
style="margin-top: 4px; text-align: center"
>
<span style="margin: 0 -5px">
&nbsp;{{ t('chart.drag_block_label_value') }}&nbsp;
</span>
</el-col>
<el-col v-if="item.term === 'between'" :span="4" style="text-align: center">
<!--between 结束值-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && !isDynamic(item)"
:span="5"
style="text-align: center"
>
<el-form-item class="form-item">
<el-input-number
v-model="item.max"
@ -392,11 +578,127 @@ init()
</el-form-item>
</el-col>
<div
style="display: flex; align-items: center; justify-content: center; margin-left: 8px"
<!--是between 动态值-->
<!--开始值 动态值字段-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && isDynamic(item)"
class="minField"
:span="3"
>
<div class="color-title">{{ t('chart.textColor') }}</div>
<el-form-item class="form-item">
<el-select v-model="item.dynamicMinField.fieldId" @change="addField(item)">
<el-option
class="series-select-option"
v-for="itemFieldOption in getConditionsFields(
fieldItem,
item,
item.dynamicMinField
)"
:key="itemFieldOption.id"
:label="itemFieldOption.name"
:value="itemFieldOption.id"
:disabled="chart.type === 'table-info' && itemFieldOption.deType === 7"
>
<el-icon style="margin-right: 8px">
<Icon
><component
:class="`field-icon-${
fieldType[[2, 3].includes(itemFieldOption.deType) ? 2 : 0]
}`"
class="svg-icon"
:is="iconFieldMap[fieldType[itemFieldOption.deType]]"
></component
></Icon>
</el-icon>
{{ itemFieldOption.name }}
</el-option>
</el-select>
</el-form-item>
</el-col>
<!--开始值 动态值聚合方式-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && isDynamic(item)"
class="minValue"
:span="2"
style="padding-left: 0 !important"
>
<el-form-item class="form-item">
<el-select v-model="item.dynamicMinField.summary" @change="changeThreshold">
<el-option
v-for="opt in getDynamicSummaryOptions(item.dynamicMinField.fieldId)"
:key="opt.id"
:label="opt.name"
:value="opt.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col
v-if="isBetween(item) && isDynamic(item)"
class="term"
:span="2"
style="margin-top: 4px; text-align: center"
>
<span style="margin: 0 -5px">
&nbsp;{{ t('chart.drag_block_label_value') }}&nbsp;
</span>
</el-col>
<!--结束值 动态值字段-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && isDynamic(item)"
class="maxField"
:span="3"
>
<el-form-item class="form-item">
<el-select v-model="item.dynamicMaxField.fieldId" @change="addField(item)">
<el-option
class="series-select-option"
v-for="itemFieldOption in getConditionsFields(
fieldItem,
item,
item.dynamicMaxField
)"
:key="itemFieldOption.id"
:label="itemFieldOption.name"
:value="itemFieldOption.id"
:disabled="chart.type === 'table-info' && itemFieldOption.deType === 7"
>
<el-icon style="margin-right: 8px">
<Icon
><component
:class="`field-icon-${
fieldType[[2, 3].includes(itemFieldOption.deType) ? 2 : 0]
}`"
class="svg-icon"
:is="iconFieldMap[fieldType[itemFieldOption.deType]]"
></component
></Icon>
</el-icon>
{{ itemFieldOption.name }}
</el-option>
</el-select>
</el-form-item>
</el-col>
<!--结束值 动态值聚合方式-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && isDynamic(item)"
class="maxValue"
:span="2"
style="padding-left: 0 !important"
>
<el-form-item class="form-item">
<el-select v-model="item.dynamicMaxField.summary" @change="changeThreshold">
<el-option
v-for="opt in getDynamicSummaryOptions(item.dynamicMaxField.fieldId)"
:key="opt.id"
:label="opt.name"
:value="opt.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item class="form-item" :label="t('chart.textColor')">
<el-color-picker
is-custom
size="large"
@ -407,12 +709,9 @@ init()
@change="changeThreshold"
/>
</el-form-item>
</div>
<div
style="display: flex; align-items: center; justify-content: center; margin-left: 8px"
>
<div class="color-title">{{ t('chart.backgroundColor') }}</div>
<el-form-item class="form-item">
</el-col>
<el-col :span="3">
<el-form-item class="form-item" :label="t('chart.backgroundColor')">
<el-color-picker
is-custom
size="large"
@ -423,22 +722,22 @@ init()
@change="changeThreshold"
/>
</el-form-item>
</div>
<div
style="display: flex; align-items: center; justify-content: center; margin-left: 8px"
>
<el-button
class="circle-button m-icon-btn"
text
@click="removeCondition(fieldItem, index)"
>
<el-icon size="20px" style="color: #646a73">
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
/></Icon>
</el-icon>
</el-button>
</div>
</el-col>
<el-col :span="1">
<div style="display: flex; align-items: center; justify-content: center">
<el-button
class="circle-button m-icon-btn"
text
@click="removeCondition(fieldItem, index)"
>
<el-icon size="20px" style="color: #646a73">
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
/></Icon>
</el-icon>
</el-button>
</div>
</el-col>
</el-row>
</el-row>

View File

@ -1074,7 +1074,19 @@ const onAssistLineChange = val => {
const onThresholdChange = val => {
view.value.senior.threshold = val
renderChart(view.value)
let type = undefined
view.value.senior.threshold?.tableThreshold?.some(item => {
if (item.conditions.some(i => i.type === 'dynamic')) {
type = 'calcData'
return true
}
return false
})
if (type) {
calcData(view.value)
} else {
renderChart(view.value)
}
}
const onMapMappingChange = val => {
@ -1766,9 +1778,7 @@ const deleteChartFieldItem = id => {
</div>
<el-popover show-arrow :offset="8" placement="bottom" width="200" trigger="click">
<template #reference>
<el-icon
v-show="route.path !== '/dvCanvas'"
style="margin-left: 4px; cursor: pointer"
<el-icon style="margin-left: 4px; cursor: pointer"
><Icon><dvInfoSvg class="svg-icon" /></Icon
></el-icon>
</template>

View File

@ -1,4 +1,12 @@
import { S2Event, S2Options, TableColCell, TableDataCell, TableSheet, ViewMeta } from '@antv/s2'
import {
type LayoutResult,
S2Event,
S2Options,
TableColCell,
TableDataCell,
TableSheet,
ViewMeta
} from '@antv/s2'
import { formatterItem, valueFormatter } from '../../../formatter'
import { parseJson } from '../../../util'
import { S2ChartView, S2DrawOptions } from '../../types/impl/s2'
@ -192,6 +200,60 @@ export class TableInfo extends S2ChartView<TableSheet> {
// 开始渲染
const newChart = new TableSheet(containerDom, s2DataConfig, s2Options)
// 自适应铺满
if (customAttr.basicStyle.tableColumnMode === 'adapt') {
newChart.on(S2Event.LAYOUT_RESIZE_COL_WIDTH, () => {
newChart.store.set('lastLayoutResult', newChart.facet.layoutResult)
})
newChart.on(S2Event.LAYOUT_AFTER_HEADER_LAYOUT, (ev: LayoutResult) => {
const status = newChart.store.get('status')
if (status === 'default') {
return
}
const lastLayoutResult = newChart.store.get('lastLayoutResult') as LayoutResult
if (status === 'expanded' && lastLayoutResult) {
// 拖拽表头定义宽度和上一次布局对比保留除已拖拽列之外的宽度
const widthByFieldValue = newChart.options.style?.colCfg?.widthByFieldValue
const lastLayoutWidthMap: Record<string, number> = lastLayoutResult?.colLeafNodes.reduce(
(p, n) => {
p[n.value] = widthByFieldValue?.[n.value] ?? n.width
return p
},
{}
)
const totalWidth = ev.colLeafNodes.reduce((p, n) => {
n.width = lastLayoutWidthMap[n.value]
n.x = p
return p + n.width
}, 0)
ev.colsHierarchy.width = totalWidth
} else {
// 第一次渲染初始化把图片字段固定为 120 进行计算
const urlFields = fields.filter(field => field.deType === 7).map(f => f.dataeaseName)
const totalWidthWithImg = ev.colLeafNodes.reduce((p, n) => {
return p + (urlFields.includes(n.field) ? 120 : n.width)
}, 0)
if (containerDom.offsetWidth <= totalWidthWithImg) {
// 图库计算的布局宽度已经大于等于容器宽度不需要再扩大不处理
newChart.store.set('status', 'default')
return
}
// 图片字段固定 120, 剩余宽度按比例均摊到其他字段进行扩大
const totalWidthWithoutImg = ev.colLeafNodes.reduce((p, n) => {
return p + (urlFields.includes(n.field) ? 0 : n.width)
}, 0)
const restWidth = containerDom.offsetWidth - urlFields.length * 120
const scale = restWidth / totalWidthWithoutImg
const totalWidth = ev.colLeafNodes.reduce((p, n) => {
n.width = urlFields.includes(n.field) ? 120 : n.width * scale
n.x = p
return p + n.width
}, 0)
ev.colsHierarchy.width = Math.min(containerDom.offsetWidth, totalWidth)
newChart.store.set('status', 'expanded')
}
})
}
// click
newChart.on(S2Event.DATA_CELL_CLICK, ev => {
const cell = newChart.getCell(ev.target)

View File

@ -4,6 +4,7 @@ import { copyContent, SortTooltip } from '@/views/chart/components/js/panel/comm
import { S2ChartView, S2DrawOptions } from '@/views/chart/components/js/panel/types/impl/s2'
import { parseJson } from '@/views/chart/components/js/util'
import {
type LayoutResult,
S2Event,
S2Options,
SHAPE_STYLE_MAP,
@ -207,7 +208,50 @@ export class TableNormal extends S2ChartView<TableSheet> {
}
// 开始渲染
const newChart = new TableSheet(containerDom, s2DataConfig, s2Options)
// 自适应铺满
if (customAttr.basicStyle.tableColumnMode === 'adapt') {
newChart.on(S2Event.LAYOUT_RESIZE_COL_WIDTH, () => {
newChart.store.set('lastLayoutResult', newChart.facet.layoutResult)
})
newChart.on(S2Event.LAYOUT_AFTER_HEADER_LAYOUT, (ev: LayoutResult) => {
const status = newChart.store.get('status')
if (status === 'default') {
return
}
const lastLayoutResult = newChart.store.get('lastLayoutResult') as LayoutResult
if (status === 'expanded' && lastLayoutResult) {
// 拖拽表头定义宽度和上一次布局对比保留除已拖拽列之外的宽度
const widthByFieldValue = newChart.options.style?.colCfg?.widthByFieldValue
const lastLayoutWidthMap: Record<string, number> = lastLayoutResult?.colLeafNodes.reduce(
(p, n) => {
p[n.value] = widthByFieldValue?.[n.value] ?? n.width
return p
},
{}
)
const totalWidth = ev.colLeafNodes.reduce((p, n) => {
n.width = lastLayoutWidthMap[n.value]
n.x = p
return p + n.width
}, 0)
ev.colsHierarchy.width = totalWidth
} else {
const scale = containerDom.offsetWidth / ev.colsHierarchy.width
if (scale <= 1) {
// 图库计算的布局宽度已经大于等于容器宽度不需要再扩大不处理
newChart.store.set('status', 'default')
return
}
const totalWidth = ev.colLeafNodes.reduce((p, n) => {
n.width = n.width * scale
n.x = p
return p + n.width
}, 0)
ev.colsHierarchy.width = Math.min(containerDom.offsetWidth, totalWidth)
newChart.store.set('status', 'expanded')
}
})
}
// click
newChart.on(S2Event.DATA_CELL_CLICK, ev => {
const cell = newChart.getCell(ev.target)

View File

@ -412,7 +412,6 @@ export function getStyle(chart: Chart): Style {
}
switch (basicStyle.tableColumnMode) {
case 'adapt': {
delete style.cellCfg.width
style.layoutWidthType = 'compact'
break
}
@ -520,6 +519,7 @@ export function getConditions(chart: Chart) {
const headerValueBgColor = isAlphaColor(tableHeader.tableHeaderBgColor)
? tableHeader.tableHeaderBgColor
: hexColorToRGBA(tableHeader.tableHeaderBgColor, basicStyle.alpha)
const filedValueMap = getFieldValueMap(chart)
for (let i = 0; i < conditions.length; i++) {
const field = conditions[i]
let defaultValueColor = valueColor
@ -541,7 +541,7 @@ export function getConditions(chart: Chart) {
return null
}
return {
fill: mappingColor(value, defaultValueColor, field, 'color')
fill: mappingColor(value, defaultValueColor, field, 'color', filedValueMap, rowData)
}
}
})
@ -554,7 +554,14 @@ export function getConditions(chart: Chart) {
if (rowData?.id && rowData?.field === rowData.id) {
return null
}
const fill = mappingColor(value, defaultBgColor, field, 'backgroundColor')
const fill = mappingColor(
value,
defaultBgColor,
field,
'backgroundColor',
filedValueMap,
rowData
)
if (isTransparent(fill)) {
return null
}
@ -566,13 +573,28 @@ export function getConditions(chart: Chart) {
return res
}
export function mappingColor(value, defaultColor, field, type) {
export function mappingColor(value, defaultColor, field, type, filedValueMap, rowData) {
let color
for (let i = 0; i < field.conditions.length; i++) {
let flag = false
const t = field.conditions[i]
if (field.field.deType === 2 || field.field.deType === 3 || field.field.deType === 4) {
const tv = parseFloat(t.value)
let tv, max, min
if (t.type === 'dynamic') {
if (t.term === 'between') {
max = parseFloat(getValue(t.dynamicMaxField, filedValueMap, rowData))
min = parseFloat(getValue(t.dynamicMinField, filedValueMap, rowData))
} else {
tv = parseFloat(getValue(t.dynamicField, filedValueMap, rowData))
}
} else {
if (t.term === 'between') {
min = parseFloat(t.min)
max = parseFloat(t.max)
} else {
tv = parseFloat(t.value)
}
}
if (t.term === 'eq') {
if (value === tv) {
color = t[type]
@ -604,8 +626,6 @@ export function mappingColor(value, defaultColor, field, type) {
flag = true
}
} else if (t.term === 'between') {
const min = parseFloat(t.min)
const max = parseFloat(t.max)
if (min <= value && value <= max) {
color = t[type]
flag = true
@ -708,6 +728,24 @@ export function mappingColor(value, defaultColor, field, type) {
return color
}
function getFieldValueMap(view) {
const fieldValueMap = {}
if (view.data && view.data.dynamicAssistLines && view.data.dynamicAssistLines.length > 0) {
view.data.dynamicAssistLines.forEach(ele => {
fieldValueMap[ele.summary + '-' + ele.fieldId] = ele.value
})
}
return fieldValueMap
}
function getValue(field, filedValueMap, rowData) {
if (field.summary === 'value') {
return rowData ? rowData[field.field?.dataeaseName] : undefined
} else {
return filedValueMap[field.summary + '-' + field.fieldId]
}
}
export function handleTableEmptyStrategy(chart: Chart) {
let newData = (chart.data?.tableRow || []) as Record<string, any>[]
let intersectionArr = []

View File

@ -172,11 +172,11 @@ const renderChart = (viewInfo: Chart, resetPageInfo: boolean) => {
recursionTransObj(customAttrTrans, actualChart.customAttr, scale.value, terminal.value)
recursionTransObj(customStyleTrans, actualChart.customStyle, scale.value, terminal.value)
setupPage(actualChart, resetPageInfo)
myChart?.facet?.timer?.stop()
myChart?.facet?.cancelScrollFrame()
myChart?.destroy()
myChart = null
setupPage(actualChart, resetPageInfo)
const chartView = chartViewManager.getChartView(
viewInfo.render,
viewInfo.type

View File

@ -260,6 +260,8 @@ const afterTreeInit = () => {
})
}
const copyLoading = ref(false)
const emit = defineEmits(['nodeClick'])
const operation = (cmd: string, data: BusiTreeNode, nodeType: string) => {
@ -291,29 +293,36 @@ const operation = (cmd: string, data: BusiTreeNode, nodeType: string) => {
id: data.id,
pid: targetPid || '0'
}
copyResource(params).then(data => {
const baseUrl =
curCanvasType.value === 'dataV'
? `#/dvCanvas?opt=copy&pid=${params.pid}&dvId=${data.data}`
: `#/dashboard?opt=copy&pid=${params.pid}&resourceId=${data.data}`
if (isEmbedded.value) {
embeddedStore.clearState()
embeddedStore.setPid(params.pid as string)
embeddedStore.setOpt('copy')
if (curCanvasType.value === 'dataV') {
embeddedStore.setDvId(data.data)
} else {
embeddedStore.setResourceId(data.data)
copyLoading.value = true
copyResource(params)
.then(data => {
const baseUrl =
curCanvasType.value === 'dataV'
? `#/dvCanvas?opt=copy&pid=${params.pid}&dvId=${data.data}`
: `#/dashboard?opt=copy&pid=${params.pid}&resourceId=${data.data}`
if (isEmbedded.value) {
embeddedStore.clearState()
embeddedStore.setPid(params.pid as string)
embeddedStore.setOpt('copy')
if (curCanvasType.value === 'dataV') {
embeddedStore.setDvId(data.data)
} else {
embeddedStore.setResourceId(data.data)
}
useEmitt().emitter.emit(
'changeCurrentComponent',
curCanvasType.value === 'dataV' ? 'VisualizationEditor' : 'DashboardEditor'
)
return
}
useEmitt().emitter.emit(
'changeCurrentComponent',
curCanvasType.value === 'dataV' ? 'VisualizationEditor' : 'DashboardEditor'
)
return
}
const newWindow = window.open(baseUrl, '_blank')
initOpenHandler(newWindow)
})
const newWindow = window.open(baseUrl, '_blank')
initOpenHandler(newWindow)
})
.finally(() => {
copyLoading.value = false
})
} else {
resourceGroupOpt.value.optInit(nodeType, data, cmd, ['copy'].includes(cmd))
}
@ -593,7 +602,7 @@ defineExpose({
</template>
</el-dropdown>
</div>
<el-scrollbar class="custom-tree">
<el-scrollbar class="custom-tree" v-loading="copyLoading">
<el-tree
menu
ref="resourceListTree"

View File

@ -1,7 +1,7 @@
<template>
<div class="info-card">
<div v-if="dvInfo.type === 'dashboard'" class="info-title">仪表板ID</div>
<div v-if="dvInfo.type === 'dashboard'" class="info-content">{{ dvInfo.id }}</div>
<div class="info-title">图表ID</div>
<div class="info-content">{{ dvInfo.id }}</div>
<div v-if="dvInfo.creatorName" class="info-title">{{ t('visualization.create_by') }}</div>
<div v-if="dvInfo.creatorName" class="info-content">{{ dvInfo.creatorName }}</div>
<div class="info-title">{{ t('visualization.create_time') }}</div>

View File

@ -928,7 +928,7 @@ defineExpose({
</div>
</template>
<template v-if="notapiexcelconfig">
<el-form-item label="连接方式" prop="type">
<el-form-item label="连接方式" prop="type" v-if="form.type !== 'es'">
<el-radio-group v-model="form.configuration.urlType">
<el-radio label="hostName">主机名</el-radio>
<el-radio label="jdbcUrl">JDBC 连接</el-radio>
@ -950,7 +950,7 @@ defineExpose({
<el-form-item
:label="t('datasource.host')"
prop="configuration.host"
v-if="form.configuration.urlType !== 'jdbcUrl'"
v-if="form.configuration.urlType !== 'jdbcUrl' && form.type !== 'es'"
>
<el-input
v-model="form.configuration.host"
@ -961,7 +961,7 @@ defineExpose({
<el-form-item
:label="t('datasource.port')"
prop="configuration.port"
v-if="form.configuration.urlType !== 'jdbcUrl'"
v-if="form.configuration.urlType !== 'jdbcUrl' && form.type !== 'es'"
>
<el-input-number
v-model="form.configuration.port"
@ -977,7 +977,7 @@ defineExpose({
<el-form-item
:label="t('datasource.data_base')"
prop="configuration.dataBase"
v-if="form.configuration.urlType !== 'jdbcUrl'"
v-if="form.configuration.urlType !== 'jdbcUrl' && form.type !== 'es'"
>
<el-input
v-model="form.configuration.dataBase"
@ -1029,6 +1029,17 @@ defineExpose({
{{ t('datasource.kerbers_info') }}
</p>
</el-form-item>
<el-form-item
v-if="form.type == 'es'"
:label="$t('datasource.datasource_url')"
prop="configuration.url"
>
<el-input
v-model="form.configuration.url"
:placeholder="$t('datasource.please_input_datasource_url')"
autocomplete="off"
/>
</el-form-item>
<el-form-item :label="t('datasource.user_name')" v-if="form.type !== 'presto'">
<el-input
:placeholder="t('common.inputText') + t('datasource.user_name')"
@ -1083,7 +1094,7 @@ defineExpose({
</el-form-item>
<el-form-item
:label="t('datasource.extra_params')"
v-if="form.configuration.urlType !== 'jdbcUrl'"
v-if="form.configuration.urlType !== 'jdbcUrl' && form.type !== 'es'"
>
<el-input
:placeholder="t('common.inputText') + t('datasource.extra_params')"

View File

@ -42,6 +42,12 @@ export const dsTypes = [
extraParams:
'characterEncoding=UTF-8&connectTimeout=5000&useSSL=false&allowPublicKeyRetrieval=true'
},
{
type: 'es',
name: 'Elasticsearch',
catalog: 'OLAP',
extraParams: ''
},
{
type: 'StarRocks',
name: 'StarRocks',

View File

@ -142,7 +142,7 @@ const nickName = ref('')
const dsName = ref('')
const userDrawer = ref(false)
const rawDatasourceList = ref([])
const showPriority = ref(true)
const showPriority = ref(false)
const showSSH = ref(true)
const datasourceEditor = ref()
const activeTab = ref('')
@ -378,6 +378,7 @@ const initSearch = () => {
state.filterTable = tableData.value.filter(ele =>
ele.tableName.toLowerCase().includes(nickName.value.toLowerCase())
)
console.log(tableData.value)
state.paginationConfig.total = state.filterTable.length
}
@ -862,6 +863,7 @@ const operation = (cmd: string, data: Tree, nodeType: string) => {
}
const handleClick = (tabName: TabPaneName) => {
console.log(tabName)
switch (tabName) {
case 'config':
listDatasourceTables({ datasourceId: nodeInfo.id }).then(res => {
@ -1372,7 +1374,7 @@ const getMenuList = (val: boolean) => {
}}</BaseInfoItem>
</el-col>
</el-row>
<template v-if="!['Excel', 'API'].includes(nodeInfo.type)">
<template v-if="!['Excel', 'API', 'es'].includes(nodeInfo.type)">
<el-row :gutter="24" v-show="nodeInfo.configuration.urlType !== 'jdbcUrl'">
<el-col :span="12">
<BaseInfoItem :label="t('datasource.host')">{{
@ -1499,6 +1501,15 @@ const getMenuList = (val: boolean) => {
</el-row>
</template>
</template>
<template v-if="['es'].includes(nodeInfo.type)">
<el-row :gutter="24">
<el-col :span="12">
<BaseInfoItem :label="t('datasource.datasource_url')">{{
nodeInfo.configuration.url
}}</BaseInfoItem>
</el-col>
</el-row>
</template>
</template>
</BaseInfoContent>
<BaseInfoContent

@ -1 +1 @@
Subproject commit ea86c71bc9060fee727ecc138e30ad8b0ae1bf84
Subproject commit d415457510f49faf552ad2bfe51030f8c228e736

View File

@ -21,6 +21,9 @@ import java.util.concurrent.ConcurrentHashMap;
public class ProviderFactory {
public static Provider getProvider(String type) throws DEException {
if (type.equalsIgnoreCase("es")) {
return SpringContextUtil.getApplicationContext().getBean("esProvider", Provider.class);
}
List<String> list = Arrays.stream(DatasourceConfiguration.DatasourceType.values()).map(DatasourceConfiguration.DatasourceType::getType).toList();
if (list.contains(type)) {
return SpringContextUtil.getApplicationContext().getBean("calciteProvider", Provider.class);

View File

@ -18,6 +18,7 @@ public class DatasourceConfiguration extends Configuration {
impala("impala", "Apache Impala", "OLAP", "`", "`"),
mariadb("mariadb", "Mariadb", "OLTP", "`", "`"),
StarRocks("StarRocks", "StarRocks", "OLAP", "`", "`"),
es("es", "Elasticsearch", "OLAP", "\"", "\""),
doris("doris", "Apache Doris", "OLAP", "`", "`"),
TiDB("TiDB", "TiDB", "OLTP", "`", "`"),
oracle("oracle", "ORACLE", "OLTP", "\"", "\""),

View File

@ -0,0 +1,19 @@
package io.dataease.extensions.view.dto;
import lombok.Data;
import java.util.List;
@Data
public class ChartSeniorThresholdCfgDTO {
/**
* 是否启用
*/
private boolean enable;
/**
* 表格阈值
*/
private List<TableThresholdDTO> tableThreshold;
}

View File

@ -0,0 +1,31 @@
package io.dataease.extensions.view.dto;
import lombok.Data;
/**
* @author jianneng
* @date 2024/9/19 18:34
**/
@Data
public class ChartSeniorThresholdDTO {
/**
* 对比方式
*/
private String term;
/**
* 类型固定值动态值
*/
private String type;
/**
* 动态值字段
*/
private ThresholdDynamicFieldDTO dynamicField;
/**
* 动态值最小值字段 仅当term为between时使用
*/
private ThresholdDynamicFieldDTO dynamicMinField;
/**
* 动态值最大值字段 仅当term为between时使用
*/
private ThresholdDynamicFieldDTO dynamicMaxField;
}

View File

@ -0,0 +1,25 @@
package io.dataease.extensions.view.dto;
import lombok.Data;
import java.util.List;
/**
* @author jianneng
* @date 2024/9/19 18:31
**/
@Data
public class TableThresholdDTO {
/**
* 字段id
*/
private String fieldId;
/**
* 字段
*/
private ChartViewFieldDTO field;
/**
* 条件
*/
private List<ChartSeniorThresholdDTO> conditions;
}

View File

@ -0,0 +1,23 @@
package io.dataease.extensions.view.dto;
import lombok.Data;
/**
* @author jianneng
* @date 2024/9/19 18:31
**/
@Data
public class ThresholdDynamicFieldDTO {
/**
* 字段id
*/
private String fieldId;
/**
* 字段
*/
private ChartViewFieldDTO field;
/**
* 条件
*/
private String summary;
}