refactor(测试跟踪): 优化功能用例导入校验

--story=1008224 --user=陈建星 用例导出/导入支持自定义字段 https://www.tapd.cn/55049933/s/1225482
This commit is contained in:
chenjianxing 2022-08-20 15:15:47 +08:00 committed by jianxing
parent f31e835893
commit 4eb0348e6c
28 changed files with 602 additions and 168 deletions

View File

@ -1,13 +1,20 @@
package io.metersphere.commons.constants;
public enum CustomFieldType {
INPUT("input"),TEXTAREA("textarea"),
SELECT("select"),MULTIPLE_SELECT("multipleSelect"),
RADIO("radio"),CHECKBOX("checkbox"),
MEMBER("member"), MULTIPLE_MEMBER("multipleMember"),
DATE("date"),DATETIME("datetime"),
INT("int"),FLOAT("float"),
MULTIPLE_INPUT("multipleInput"),RICH_TEXT("richText");
INPUT("input"),
TEXTAREA("textarea"),
SELECT("select"),
MULTIPLE_SELECT("multipleSelect"),
RADIO("radio"),
CHECKBOX("checkbox"),
MEMBER("member"),
MULTIPLE_MEMBER("multipleMember"),
DATE("date"),
DATETIME("datetime"),
INT("int"),
FLOAT("float"),
MULTIPLE_INPUT("multipleInput"),
RICH_TEXT("richText");
String value;

View File

@ -1,5 +0,0 @@
package io.metersphere.commons.constants;
public enum TestCaseStatus {
performance,api,testcase,automation
}

View File

@ -0,0 +1,5 @@
package io.metersphere.commons.constants;
public enum TestCaseTestStatus {
performance, api, testcase, automation
}

View File

@ -0,0 +1,13 @@
package io.metersphere.commons.exception;
public class CustomFieldValidateException extends Exception {
private static final long serialVersionUID = 1L;
public CustomFieldValidateException(String message) {
super(message);
}
public static void throwException(String message) throws CustomFieldValidateException {
throw new CustomFieldValidateException(message);
}
}

View File

@ -6,6 +6,15 @@ import lombok.Setter;
@Setter
@Getter
public class CustomFieldOption {
private String text;
private String value;
private String text;
private Boolean system = false;
public CustomFieldOption(String value, String text, Boolean system) {
this.text = text;
this.value = value;
this.system = system;
}
public CustomFieldOption() {}
}

View File

@ -14,35 +14,41 @@ import java.util.function.Function;
public enum TestCaseImportFiled {
ID("ID", "ID", "ID", TestCaseExcelData::getCustomNum),
NAME("用例名称", "用例名稱", "Name", TestCaseExcelData::getName),
MODULE("所属模块", "所屬模塊", "Module", TestCaseExcelData::getNodePath),
TAG("标签", "標簽", "Tag", TestCaseImportFiled::parseTags),
PREREQUISITE("前置条件", "前置條件", "Prerequisite", TestCaseExcelData::getPrerequisite),
REMARK("备注", "備註", "Remark", TestCaseExcelData::getRemark),
STEP_DESC("步骤描述", "步驟描述", "Step description", TestCaseExcelData::getStepDesc),
STEP_RESULT("预期结果", "預期結果", "Step result", TestCaseExcelData::getStepResult),
STEP_MODEL("编辑模式", "編輯模式", "Edit Model", TestCaseExcelData::getStepModel),
STATUS("用例状态", "用例狀態", "Case status", TestCaseExcelData::getStatus),
MAINTAINER("责任人", "責任人", "Maintainer", TestCaseExcelData::getMaintainer),
PRIORITY("用例等级", "用例等級", "Priority", TestCaseExcelData::getPriority);
ID("id", "ID", "ID", "ID", TestCaseExcelData::getCustomNum),
NAME("name", "用例名称", "用例名稱", "Name", TestCaseExcelData::getName),
MODULE("module","所属模块", "所屬模塊", "Module", TestCaseExcelData::getNodePath),
TAGS("tags","标签", "標簽", "Tag", TestCaseImportFiled::parseTags),
PREREQUISITE("prerequisite","前置条件", "前置條件", "Prerequisite", TestCaseExcelData::getPrerequisite),
REMARK("remark","备注", "備註", "Remark", TestCaseExcelData::getRemark),
STEP_DESC("stepDesc","步骤描述", "步驟描述", "Step description", TestCaseExcelData::getStepDesc),
STEP_RESULT("stepResult","预期结果", "預期結果", "Step result", TestCaseExcelData::getStepResult),
STEP_MODEL("stepModel","编辑模式", "編輯模式", "Edit Model", TestCaseExcelData::getStepModel),
STATUS("status","用例状态", "用例狀態", "Case status", TestCaseExcelData::getStatus),
MAINTAINER("maintainer","责任人", "責任人", "Maintainer", TestCaseExcelData::getMaintainer),
PRIORITY("priority","用例等级", "用例等級", "Priority", TestCaseExcelData::getPriority);
private Map<Locale, String> filedLangMap;
private Function<TestCaseExcelData, String> parseFunc;
private String value;
public Map<Locale, String> getFiledLangMap() {
return this.filedLangMap;
}
TestCaseImportFiled(String zn, String chineseTw, String us, Function<TestCaseExcelData, String> parseFunc) {
TestCaseImportFiled(String value, String zn, String chineseTw, String us, Function<TestCaseExcelData, String> parseFunc) {
this.filedLangMap = new HashMap<>() {{
put(Locale.SIMPLIFIED_CHINESE, zn);
put(Locale.TRADITIONAL_CHINESE, chineseTw);
put(Locale.US, us);
}};
this.value = value;
this.parseFunc = parseFunc;
}
public Map<Locale, String> getFiledLangMap() {
return this.filedLangMap;
}
public String getValue() {
return value;
}
public String parseExcelDataValue(TestCaseExcelData excelData) {
return parseFunc.apply(excelData);
}

View File

@ -48,7 +48,7 @@ public class TestCaseExcelData {
@ExcelIgnore
private String priority;
@ExcelIgnore
Map<String, String> customDatas = new LinkedHashMap<>();
Map<String, String> customData = new LinkedHashMap<>();
@ExcelIgnore
List<String> mergeStepDesc;

View File

@ -61,18 +61,6 @@ public class TestCaseExcelDataCn extends TestCaseExcelData {
@Pattern(regexp = "(^TEXT$)|(^STEP$)|(.{0})", message = "{test_case_step_model_validate}")
private String stepModel;
@ColumnWidth(50)
@ExcelProperty("用例状态")
private String status;
@ExcelProperty(value = "责任人(ID)")
private String maintainer;
@NotBlank(message = "{cannot_be_null}")
@ExcelProperty("用例等级")
@Pattern(regexp = "(^P0$)|(^P1$)|(^P2$)|(^P3$)", message = "{test_case_priority_validate}")
private String priority;
@Override
public List<List<String>> getHead(boolean needNum, List<CustomFieldDao> customFields) {
return super.getHead(needNum, customFields, Locale.SIMPLIFIED_CHINESE);

View File

@ -61,18 +61,6 @@ public class TestCaseExcelDataTw extends TestCaseExcelData {
@Pattern(regexp = "(^TEXT$)|(^STEP$)|(.{0})", message = "{test_case_step_model_validate}")
private String stepModel;
@ColumnWidth(50)
@ExcelProperty("用例狀態")
private String status;
@NotBlank(message = "{cannot_be_null}")
@ExcelProperty("用例等級")
@Pattern(regexp = "(^P0$)|(^P1$)|(^P2$)|(^P3$)", message = "{test_case_priority_validate}")
private String priority;
@ExcelProperty("責任人(ID)")
private String maintainer;
@Override
public List<List<String>> getHead(boolean needNum, List<CustomFieldDao> customFields) {
return super.getHead(needNum, customFields, Locale.TRADITIONAL_CHINESE);

View File

@ -62,17 +62,6 @@ public class TestCaseExcelDataUs extends TestCaseExcelData {
@Pattern(regexp = "(^TEXT$)|(^STEP$)|(.{0})", message = "{test_case_step_model_validate}")
private String stepModel;
@ColumnWidth(50)
@ExcelProperty("Case status")
private String status;
@ExcelProperty("Priority")
@Pattern(regexp = "(^P0$)|(^P1$)|(^P2$)|(^P3$)", message = "{test_case_priority_validate}")
private String priority;
@ExcelProperty("Maintainer(ID)")
private String maintainer;
@Override
public List<List<String>> getHead(boolean needNum, List<CustomFieldDao> customFields) {
return super.getHead(needNum, customFields, Locale.US);

View File

@ -58,7 +58,7 @@ public class FunctionCaseTemplateWriteHandler implements RowWriteHandler {
maintainerIndex = index;
} else if (TestCaseImportFiled.PRIORITY.containsHead(head)) {
priorityIndex = index;
} else if (TestCaseImportFiled.TAG.containsHead(head)) {
} else if (TestCaseImportFiled.TAGS.containsHead(head)) {
tagIndex = index;
} else if (TestCaseImportFiled.STATUS.containsHead(head)) {
statusIndex = index;

View File

@ -8,13 +8,12 @@ import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.TestCase;
import io.metersphere.base.domain.TestCaseWithBLOBs;
import io.metersphere.commons.constants.TestCaseConstants;
import io.metersphere.commons.exception.CustomFieldValidateException;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.dto.CustomFieldOption;
import io.metersphere.excel.annotation.NotRequired;
import io.metersphere.excel.constants.TestCaseImportFiled;
import io.metersphere.excel.domain.ExcelErrData;
@ -26,6 +25,8 @@ import io.metersphere.excel.utils.FunctionCaseImportEnum;
import io.metersphere.i18n.Translator;
import io.metersphere.track.request.testcase.TestCaseImportRequest;
import io.metersphere.track.service.TestCaseService;
import io.metersphere.track.validate.AbstractCustomFieldValidator;
import io.metersphere.track.validate.CustomFieldValidatorFactory;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@ -97,6 +98,8 @@ public class TestCaseNoModelDataListener extends AnalysisEventListener<Map<Integ
*/
private HashMap<ExcelMergeInfo, String> mergeCellDataMap = new HashMap<>();
private HashMap<String, AbstractCustomFieldValidator> customFieldValidatorMap;
public boolean isUpdated() {
return isUpdated;
}
@ -109,23 +112,11 @@ public class TestCaseNoModelDataListener extends AnalysisEventListener<Map<Integ
this.request = request;
this.customFieldValidatorMap = CustomFieldValidatorFactory.getValidatorMap();
List<CustomFieldDao> customFields = request.getCustomFields();
if (CollectionUtils.isNotEmpty(customFields)) {
for (CustomFieldDao dto : customFields) {
String name = dto.getName();
if (StringUtils.isNotEmpty(name)) {
name = name.trim();
}
if (TestCaseImportFiled.MAINTAINER.getFiledLangMap().values().contains(name)) {
customFieldsMap.put("maintainer", dto);
} else if (TestCaseImportFiled.PRIORITY.getFiledLangMap().values().contains(name)) {
customFieldsMap.put("priority", dto);
} else if (TestCaseImportFiled.STATUS.getFiledLangMap().values().contains(name)) {
customFieldsMap.put("status", dto);
} else {
customFieldsMap.put(name, dto);
}
}
customFieldsMap = customFields.stream().collect(Collectors.toMap(CustomFieldDao::getName, i -> i));
}
}
@ -377,48 +368,36 @@ public class TestCaseNoModelDataListener extends AnalysisEventListener<Map<Integ
return StringUtils.equals(request.getImportType(), FunctionCaseImportEnum.Create.name());
}
/**
* 校验自定义字段并记录错误提示
* 如果填写的是自定义字段的选项值则转换成ID保存
* @param data
* @param stringBuilder
*/
private void validateCustomField(TestCaseExcelData data, StringBuilder stringBuilder) {
//校验自定义必填字段
for (Map.Entry<String, CustomFieldDao> customEntry : customFieldsMap.entrySet()) {
String customName = customEntry.getKey();
CustomFieldDao field = customEntry.getValue();
if (field.getRequired()) {
String value;
if (StringUtils.equals(customName, "status")) {
Map<String, String> valueMap = new HashMap<>() {{
put("Prepare", Translator.get("test_case_status_prepare"));
put("Underway", Translator.get("test_case_status_running"));
put("Completed", Translator.get("test_case_status_finished"));
}};
value = getCustomFieldValue(Translator.get("test_case_status"), data.getStatus(), field.getOptions(),
stringBuilder, valueMap);
data.setStatus(value);
} else if (StringUtils.equals(customName, "priority")) {
value = data.getPriority();
} else if (StringUtils.equals(customName, "maintainer")) {
value = data.getMaintainer();
//校验维护人
if (StringUtils.isBlank(value)) {
data.setMaintainer(SessionUtils.getUserId());
} else {
if (!request.getUserIds().contains(value)) {
stringBuilder.append(Translator.get("user_not_exists"))
.append( "")
.append(value)
.append(ERROR_MSG_SEPARATOR);
}
}
continue;
} else {
value = data.getCustomDatas().get(customName);
}
if (StringUtils.isEmpty(value)) {
stringBuilder.append(field.getName())
.append(" ")
.append(Translator.get("required"))
.append(ERROR_MSG_SEPARATOR);
}
Map<String, String> customData = data.getCustomData();
for (String fieldName : customData.keySet()) {
String value = customData.get(fieldName);
CustomFieldDao customField = customFieldsMap.get(fieldName);
if (customField == null) {
continue;
}
AbstractCustomFieldValidator customFieldValidator = customFieldValidatorMap.get(customField.getType());
try {
customFieldValidator.validate(customField, value);
} catch (CustomFieldValidateException e) {
stringBuilder.append(e.getMessage().concat(ERROR_MSG_SEPARATOR));
}
if (customFieldValidator.isKVOption) {
// 这里如果填的是选项值替换成选项ID保存
customData.put(fieldName, customFieldValidator.parse2Key(value, customField));
}
if (StringUtils.equals(fieldName, TestCaseImportFiled.STATUS.getValue())) {
data.setStatus(value);
} else if (StringUtils.equals(fieldName, TestCaseImportFiled.PRIORITY.getValue())) {
data.setPriority(value);
} else if (StringUtils.equals(fieldName, TestCaseImportFiled.MAINTAINER.getValue())) {
data.setMaintainer(value);
}
}
}
@ -485,37 +464,6 @@ public class TestCaseNoModelDataListener extends AnalysisEventListener<Map<Integ
}
}
/**
* 根据自定义字段的选项值获取对应的选项id
*
* @param name
* @param text
* @param optionsStr
* @return
*/
public String getCustomFieldValue(String name, String text, String optionsStr, StringBuilder error,
Map<String, String> systemValueMap) {
List<String> textList = new ArrayList<>();
if (StringUtils.isNotEmpty(optionsStr)) {
List<CustomFieldOption> options = JSONObject.parseArray(optionsStr, CustomFieldOption.class);
for (CustomFieldOption option : options) {
// 系统字段需要翻译
String i18nText = systemValueMap.get(option.getValue());
if (i18nText != null) {
option.setText(i18nText);
}
if (StringUtils.equals(option.getValue(), text) ||
// 系统字段填写对应内容而不是key的情况比如用例状态填未开始而不是 Prepare
StringUtils.equals(option.getText(), text)) {
return option.getValue();
}
textList.add(option.getText());
}
}
error.append(String.format(Translator.get("custom_field_option_not_exist"), name, textList.toString()));
return text;
}
public List<String> getNames() {
return this.names;
}
@ -820,14 +768,8 @@ public class TestCaseNoModelDataListener extends AnalysisEventListener<Map<Integ
data.setStepResult(value);
} else if (StringUtils.equals(field, "stepModel")) {
data.setStepModel(value);
} else if (StringUtils.equals(field, "status")) {
data.setStatus(value);
} else if (StringUtils.equals(field, "maintainer")) {
data.setMaintainer(value);
} else if (StringUtils.equals(field, "priority")) {
data.setPriority(value);
} else {
data.getCustomDatas().put(field, value);
data.getCustomData().put(field, value);
}
}
return data;

View File

@ -0,0 +1,17 @@
package io.metersphere.track.constants;
public enum TestCaseStatus {
Prepare("test_case_status_prepare"),
Underway("test_case_status_prepare"),
Completed("test_case_status_finished");
private String i18nKey;
TestCaseStatus(String i18nKey) {
this.i18nKey = i18nKey;
}
public String getI18nKey() {
return i18nKey;
}
}

View File

@ -1462,7 +1462,7 @@ public class TestCaseService {
for (TestCaseExcelData model : data) {
List<Object> fields = new ArrayList<>();
Map<String, String> customDataMaps = Optional.ofNullable(model.getCustomDatas())
Map<String, String> customDataMaps = Optional.ofNullable(model.getCustomData())
.orElse(new HashMap<>());
Map<String, String> otherFieldMaps = Optional.ofNullable(model.getOtherFields())
.orElse(new HashMap<>());
@ -1626,7 +1626,7 @@ public class TestCaseService {
map.put(customNameMap.get(id), value);
}
}
data.setCustomDatas(map);
data.setCustomData(map);
} catch (Exception e) {
LogUtil.error(e);
}

View File

@ -627,7 +627,7 @@ public class TestPlanService {
Long nextScenarioOrder = ServiceUtils.getNextOrder(request.getPlanId(), extTestPlanScenarioCaseMapper::getLastOrder);
for (TestCaseTest l : list) {
if (StringUtils.equals(l.getTestType(), TestCaseStatus.performance.name())) {
if (StringUtils.equals(l.getTestType(), TestCaseTestStatus.performance.name())) {
String id = l.getTestId();
LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(id);
TestPlanLoadCaseWithBLOBs t = new TestPlanLoadCaseWithBLOBs();
@ -650,7 +650,7 @@ public class TestPlanService {
}
}
if (StringUtils.equals(l.getTestType(), TestCaseStatus.testcase.name())) {
if (StringUtils.equals(l.getTestType(), TestCaseTestStatus.testcase.name())) {
TestPlanApiCase t = new TestPlanApiCase();
ApiTestCaseWithBLOBs apitest = apiTestCaseMapper.selectByPrimaryKey(l.getTestId());
if (null != apitest) {
@ -674,7 +674,7 @@ public class TestPlanService {
}
if (StringUtils.equals(l.getTestType(), TestCaseStatus.automation.name())) {
if (StringUtils.equals(l.getTestType(), TestCaseTestStatus.automation.name())) {
TestPlanApiScenario t = new TestPlanApiScenario();
ApiScenarioWithBLOBs testPlanApiScenario = apiScenarioMapper.selectByPrimaryKey(l.getTestId());
if (testPlanApiScenario != null) {

View File

@ -0,0 +1,50 @@
package io.metersphere.track.validate;
import com.alibaba.fastjson.JSONArray;
import io.metersphere.commons.exception.CustomFieldValidateException;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.i18n.Translator;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
public abstract class AbstractCustomFieldValidator {
/**
* 标记是否是键值对的选项
* 需要校验时可以填键也可以填值
*/
public Boolean isKVOption = false;
/**
* 校验参数是否合法
* @param customField
* @param value
*/
abstract public void validate(CustomFieldDao customField, String value) throws CustomFieldValidateException;
/**
* 将选项的值转化为对应的key
* @param keyOrValue
* @return
*/
public String parse2Key(String keyOrValue, CustomFieldDao customField) {
return keyOrValue;
}
protected void validateRequired(CustomFieldDao customField, String value) throws CustomFieldValidateException {
if (customField.getRequired() && StringUtils.isBlank(value)) {
CustomFieldValidateException.throwException(String.format(Translator.get("custom_field_required_tip"), customField.getName()));
}
}
protected List<String> parse2Array(String name, String value) throws CustomFieldValidateException {
try {
return JSONArray.parseArray(value, String.class);
} catch (Exception e) {
CustomFieldValidateException.throwException(String.format(Translator.get("custom_field_required_tip"), name));
}
return new ArrayList<>();
}
}

View File

@ -0,0 +1,22 @@
package io.metersphere.track.validate;
import io.metersphere.commons.exception.CustomFieldValidateException;
import io.metersphere.commons.utils.DateUtils;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.i18n.Translator;
import org.apache.commons.lang3.StringUtils;
public class CustomFieldDateTimeValidator extends AbstractCustomFieldValidator {
@Override
public void validate(CustomFieldDao customField, String value) throws CustomFieldValidateException {
validateRequired(customField, value);
try {
if (StringUtils.isNotBlank(value)) {
DateUtils.getTime(value);
}
} catch (Exception e) {
CustomFieldValidateException.throwException(String.format(Translator.get("custom_field_datetime_tip"), customField.getName(), DateUtils.TIME_PATTERN));
}
}
}

View File

@ -0,0 +1,21 @@
package io.metersphere.track.validate;
import io.metersphere.commons.exception.CustomFieldValidateException;
import io.metersphere.commons.utils.DateUtils;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.i18n.Translator;
import org.apache.commons.lang3.StringUtils;
public class CustomFieldDateValidator extends AbstractCustomFieldValidator {
public void validate(CustomFieldDao customField, String value) throws CustomFieldValidateException {
validateRequired(customField, value);
try {
if (StringUtils.isNotBlank(value)) {
DateUtils.getDate(value);
}
} catch (Exception e) {
CustomFieldValidateException.throwException(String.format(Translator.get("custom_field_date_tip"), customField.getName(), DateUtils.DATE_PATTERM));
}
}
}

View File

@ -0,0 +1,20 @@
package io.metersphere.track.validate;
import io.metersphere.commons.exception.CustomFieldValidateException;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.i18n.Translator;
import org.apache.commons.lang3.StringUtils;
public class CustomFieldFloatValidator extends AbstractCustomFieldValidator {
public void validate(CustomFieldDao customField, String value) throws CustomFieldValidateException {
validateRequired(customField, value);
try {
if (StringUtils.isNotBlank(value)) {
Float.parseFloat(value);
}
} catch (Exception e) {
CustomFieldValidateException.throwException(String.format(Translator.get("custom_field_float_tip"), customField.getName()));
}
}
}

View File

@ -0,0 +1,20 @@
package io.metersphere.track.validate;
import io.metersphere.commons.exception.CustomFieldValidateException;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.i18n.Translator;
import org.apache.commons.lang3.StringUtils;
public class CustomFieldIntegerValidator extends AbstractCustomFieldValidator {
public void validate(CustomFieldDao customField, String value) throws CustomFieldValidateException {
validateRequired(customField, value);
try {
if (StringUtils.isNotBlank(value)) {
Integer.parseInt(value);
}
} catch (Exception e) {
CustomFieldValidateException.throwException(String.format(Translator.get("custom_field_int_tip"), customField.getName()));
}
}
}

View File

@ -0,0 +1,48 @@
package io.metersphere.track.validate;
import io.metersphere.base.domain.User;
import io.metersphere.commons.exception.CustomFieldValidateException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.i18n.Translator;
import io.metersphere.service.UserService;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CustomFieldMemberValidator extends AbstractCustomFieldValidator {
protected Map<String, String> userIdMap;
protected Map<String, String> userNameMap;
public CustomFieldMemberValidator() {
this.isKVOption = true;
UserService userService = CommonBeanFactory.getBean(UserService.class);
List<User> memberOption = userService.getProjectMemberOption(SessionUtils.getCurrentProjectId());
userIdMap = memberOption.stream().collect(Collectors.toMap(User::getId, User::getName));
userNameMap = memberOption.stream().collect(Collectors.toMap(User::getName, User::getId));
}
@Override
public void validate(CustomFieldDao customField, String value) throws CustomFieldValidateException {
validateRequired(customField, value);
if (StringUtils.isBlank(value)) {
return;
}
if (userIdMap.containsKey(value) || userNameMap.containsKey(value)) {
return;
}
throw new CustomFieldValidateException(String.format(Translator.get("custom_field_member_tip"), customField.getName()));
}
@Override
public String parse2Key(String keyOrValue, CustomFieldDao customField) {
if (userNameMap.containsKey(keyOrValue)) {
return userNameMap.get(keyOrValue);
}
return keyOrValue;
}
}

View File

@ -0,0 +1,39 @@
package io.metersphere.track.validate;
import com.alibaba.fastjson.JSONArray;
import io.metersphere.commons.exception.CustomFieldValidateException;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.i18n.Translator;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
public class CustomFieldMultipleMemberValidator extends CustomFieldMemberValidator {
@Override
public void validate(CustomFieldDao customField, String value) throws CustomFieldValidateException {
validateRequired(customField, value);
if (StringUtils.isBlank(value)) {
return;
}
for (String item : parse2Array(customField.getName(), value)) {
if (!userIdMap.containsKey(item) && !userNameMap.containsKey(item)) {
CustomFieldValidateException.throwException(String.format(Translator.get("custom_field_member_tip"), customField.getName()));
}
}
}
@Override
public String parse2Key(String keyOrValuesStr, CustomFieldDao customField) {
List<String> keyOrValues = JSONArray.parseArray(keyOrValuesStr, String.class);
for (int i = 0; i < keyOrValues.size(); i++) {
String item = keyOrValues.get(i);
if (userNameMap.containsKey(item)) {
keyOrValues.set(i, userNameMap.get(item));
}
}
return JSONArray.toJSONString(keyOrValues);
}
}

View File

@ -0,0 +1,42 @@
package io.metersphere.track.validate;
import com.alibaba.fastjson.JSONArray;
import io.metersphere.commons.exception.CustomFieldValidateException;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.i18n.Translator;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class CustomFieldMultipleSelectValidator extends CustomFieldSelectValidator {
@Override
public void validate(CustomFieldDao customField, String value) throws CustomFieldValidateException {
validateRequired(customField, value);
if (StringUtils.isBlank(value)) {
return;
}
Set<String> idSet = optionValueSetCache.get(customField.getId());
Set<String> textSet = optionTextSetCache.get(customField.getId());
for (String item : parse2Array(customField.getName(), value)) {
if (!idSet.contains(item) && !textSet.contains(item)) {
CustomFieldValidateException.throwException(String.format(Translator.get("custom_field_select_tip"), customField.getName(), textSet));
}
}
}
@Override
public String parse2Key(String keyOrValuesStr, CustomFieldDao customField) {
List<String> keyOrValues = JSONArray.parseArray(keyOrValuesStr, String.class);
Map<String, String> nameMap = optionTextMapCache.get(customField.getId());
for (int i = 0; i < keyOrValues.size(); i++) {
String item = keyOrValues.get(i);
if (nameMap.containsKey(item)) {
keyOrValues.set(i, nameMap.get(item));
}
}
return JSONArray.toJSONString(keyOrValues);
}
}

View File

@ -0,0 +1,21 @@
package io.metersphere.track.validate;
import com.alibaba.fastjson.JSONArray;
import io.metersphere.commons.exception.CustomFieldValidateException;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.i18n.Translator;
import org.apache.commons.lang3.StringUtils;
public class CustomFieldMultipleTextValidator extends AbstractCustomFieldValidator {
public void validate(CustomFieldDao customField, String value) throws CustomFieldValidateException {
validateRequired(customField, value);
if (StringUtils.isNotBlank(value)) {
try {
JSONArray.parseArray(value);
} catch (Exception e) {
CustomFieldValidateException.throwException(String.format(Translator.get("custom_field_array_tip"), customField.getName()));
}
}
}
}

View File

@ -0,0 +1,140 @@
package io.metersphere.track.validate;
import com.alibaba.fastjson.JSONArray;
import io.metersphere.commons.exception.CustomFieldValidateException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.dto.CustomFieldOption;
import io.metersphere.excel.constants.TestCaseImportFiled;
import io.metersphere.i18n.Translator;
import io.metersphere.track.constants.TestCaseStatus;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.stream.Collectors;
public class CustomFieldSelectValidator extends AbstractCustomFieldValidator {
/**
* 缓存每个字段对应的选项值
*/
Map<String, List<CustomFieldOption>> optionCache = new HashMap<>();
Map<String, Set<String>> optionValueSetCache = new HashMap<>();
Map<String, Set<String>> optionTextSetCache = new HashMap<>();
Map<String, Map<String, String>> optionTextMapCache = new HashMap<>();
/**
* 保存系统字段中选项翻译后的值
* key 为字段名称value 为选项value和选项值的映射
*/
Map<String, Map<String, String>> i18nMap = new HashMap<>();
public CustomFieldSelectValidator() {
Map<String, String> statusI18nMap = new HashMap<>();
for (TestCaseStatus status : TestCaseStatus.values()) {
statusI18nMap.put(status.name(), Translator.get(status.getI18nKey()));
}
i18nMap.put(TestCaseImportFiled.STATUS.getFiledLangMap().get(Locale.SIMPLIFIED_CHINESE), statusI18nMap);
this.isKVOption = true;
}
public void validate(CustomFieldDao customField, String value) throws CustomFieldValidateException {
validateRequired(customField, value);
if (StringUtils.isBlank(value)) {
return;
}
prepareCatch(customField);
Set<String> idSet = optionValueSetCache.get(customField.getId());
Set<String> textSet = optionTextSetCache.get(customField.getId());
if (!idSet.contains(value) && !textSet.contains(value)) {
CustomFieldValidateException.throwException(String.format(Translator.get("custom_field_select_tip"), customField.getName(), textSet));
}
}
@Override
public String parse2Key(String keyOrValuesStr, CustomFieldDao customField) {
Map<String, String> textMap = optionTextMapCache.get(customField.getId());
if (MapUtils.isNotEmpty(textMap) && textMap.containsKey(keyOrValuesStr)) {
return textMap.get(keyOrValuesStr);
}
return keyOrValuesStr;
}
/**
* 获取自定义字段的选项值和key
* 存储到缓存中增强导入时性能
*
* @param customField
*/
private void prepareCatch(CustomFieldDao customField) {
if (optionValueSetCache.get(customField.getId()) == null) {
List<CustomFieldOption> options = getOptions(customField.getId(), customField.getOptions());
translateSystemOption(customField, options);
optionValueSetCache.put(customField.getId(), getIdSet(options));
optionTextSetCache.put(customField.getId(), getNameSet(options));
optionTextMapCache.put(customField.getId(), getTextMap(options));
}
}
/**
* 翻译系统字段的选项名称
* @param customField
* @param options
*/
private void translateSystemOption(CustomFieldDao customField, List<CustomFieldOption> options) {
Map<String, String> fieldI18nMap = i18nMap.get(customField.getName());
if (fieldI18nMap != null) {
// 不为空说明需要翻译
Iterator<CustomFieldOption> iterator = options.iterator();
// 先将系统字段删掉
while (iterator.hasNext()) {
CustomFieldOption option = iterator.next();
if (option.getSystem()) {
iterator.remove();
}
}
// 再填充翻译后的值
for (String optionValue : fieldI18nMap.keySet()) {
CustomFieldOption option = new CustomFieldOption(optionValue, fieldI18nMap.get(optionValue), true);
options.add(option);
}
}
}
@NotNull
private Map<String, String> getTextMap(List<CustomFieldOption> options) {
return options.stream()
.collect(Collectors.toMap(CustomFieldOption::getValue, CustomFieldOption::getText));
}
@NotNull
protected Set<String> getNameSet(List<CustomFieldOption> options) {
return options.stream()
.map(CustomFieldOption::getText)
.collect(Collectors.toSet());
}
@NotNull
protected Set<String> getIdSet(List<CustomFieldOption> options) {
return options.stream()
.map(CustomFieldOption::getValue)
.collect(Collectors.toSet());
}
protected List<CustomFieldOption> getOptions(String id, String optionsStr) {
List<CustomFieldOption> options = optionCache.get(id);
if (options != null) {
return options;
}
try {
return JSONArray.parseArray(optionsStr, CustomFieldOption.class);
} catch (Exception e) {
LogUtil.error(e);
}
return new ArrayList<>();
}
}

View File

@ -0,0 +1,11 @@
package io.metersphere.track.validate;
import io.metersphere.commons.exception.CustomFieldValidateException;
import io.metersphere.dto.CustomFieldDao;
public class CustomFieldTextValidator extends AbstractCustomFieldValidator {
public void validate(CustomFieldDao customField, String value) throws CustomFieldValidateException {
validateRequired(customField, value);
}
}

View File

@ -0,0 +1,33 @@
package io.metersphere.track.validate;
import io.metersphere.commons.constants.CustomFieldType;
import java.util.HashMap;
public class CustomFieldValidatorFactory {
public static HashMap<String, AbstractCustomFieldValidator> getValidatorMap() {
return new HashMap<>() {{
put(CustomFieldType.SELECT.getValue(), new CustomFieldSelectValidator());
put(CustomFieldType.RADIO.getValue(), new CustomFieldSelectValidator());
put(CustomFieldType.MULTIPLE_SELECT.getValue(), new CustomFieldMultipleSelectValidator());
put(CustomFieldType.CHECKBOX.getValue(), new CustomFieldMultipleSelectValidator());
put(CustomFieldType.INPUT.getValue(), new CustomFieldTextValidator());
put(CustomFieldType.RICH_TEXT.getValue(), new CustomFieldTextValidator());
put(CustomFieldType.TEXTAREA.getValue(), new CustomFieldTextValidator());
put(CustomFieldType.MULTIPLE_INPUT.getValue(), new CustomFieldMultipleTextValidator());
put(CustomFieldType.DATE.getValue(), new CustomFieldDateValidator());
put(CustomFieldType.DATETIME.getValue(), new CustomFieldDateTimeValidator());
put(CustomFieldType.MEMBER.getValue(), new CustomFieldMemberValidator());
put(CustomFieldType.MULTIPLE_MEMBER.getValue(), new CustomFieldMultipleMemberValidator());
put(CustomFieldType.INT.getValue(), new CustomFieldIntegerValidator());
put(CustomFieldType.FLOAT.getValue(), new CustomFieldFloatValidator());
}};
}
}

View File

@ -323,6 +323,14 @@ test_case_plan_comment=执行
test_case_review_comment=评审
plan_case_status_blocking=阻塞
plan_case_status_skip=跳过
custom_field_required_tip=[%s]为必填项
custom_field_array_tip=[%s]必须是数组
custom_field_datetime_tip=[%s]必须为时间日期格式[%s]
custom_field_date_tip=[%s]必须为日期格式[%s]
custom_field_float_tip=[%s]必须为数字
custom_field_int_tip=[%s]必须为整型
custom_field_member_tip=[%s]必须当前项目成员
custom_field_select_tip=[%s]必须为%s
# mock
mock_warning=未找到匹配的Mock期望
zentao_test_type_error=无效的 Zentao 请求