feat: jira支持自动获取模板

This commit is contained in:
chenjianxing 2021-12-10 22:04:02 +08:00 committed by jianxing
parent bc5381a7c4
commit a3ccff0649
39 changed files with 819 additions and 456 deletions

View File

@ -45,5 +45,9 @@ public class Project implements Serializable {
private String azureFilterId;
private String platform;
private Boolean thirdPartTemplate;
private static final long serialVersionUID = 1L;
}
}

View File

@ -1433,6 +1433,136 @@ public class ProjectExample {
addCriterion("azure_filter_id not between", value1, value2, "azureFilterId");
return (Criteria) this;
}
public Criteria andPlatformIsNull() {
addCriterion("platform is null");
return (Criteria) this;
}
public Criteria andPlatformIsNotNull() {
addCriterion("platform is not null");
return (Criteria) this;
}
public Criteria andPlatformEqualTo(String value) {
addCriterion("platform =", value, "platform");
return (Criteria) this;
}
public Criteria andPlatformNotEqualTo(String value) {
addCriterion("platform <>", value, "platform");
return (Criteria) this;
}
public Criteria andPlatformGreaterThan(String value) {
addCriterion("platform >", value, "platform");
return (Criteria) this;
}
public Criteria andPlatformGreaterThanOrEqualTo(String value) {
addCriterion("platform >=", value, "platform");
return (Criteria) this;
}
public Criteria andPlatformLessThan(String value) {
addCriterion("platform <", value, "platform");
return (Criteria) this;
}
public Criteria andPlatformLessThanOrEqualTo(String value) {
addCriterion("platform <=", value, "platform");
return (Criteria) this;
}
public Criteria andPlatformLike(String value) {
addCriterion("platform like", value, "platform");
return (Criteria) this;
}
public Criteria andPlatformNotLike(String value) {
addCriterion("platform not like", value, "platform");
return (Criteria) this;
}
public Criteria andPlatformIn(List<String> values) {
addCriterion("platform in", values, "platform");
return (Criteria) this;
}
public Criteria andPlatformNotIn(List<String> values) {
addCriterion("platform not in", values, "platform");
return (Criteria) this;
}
public Criteria andPlatformBetween(String value1, String value2) {
addCriterion("platform between", value1, value2, "platform");
return (Criteria) this;
}
public Criteria andPlatformNotBetween(String value1, String value2) {
addCriterion("platform not between", value1, value2, "platform");
return (Criteria) this;
}
public Criteria andThirdPartTemplateIsNull() {
addCriterion("third_part_template is null");
return (Criteria) this;
}
public Criteria andThirdPartTemplateIsNotNull() {
addCriterion("third_part_template is not null");
return (Criteria) this;
}
public Criteria andThirdPartTemplateEqualTo(Boolean value) {
addCriterion("third_part_template =", value, "thirdPartTemplate");
return (Criteria) this;
}
public Criteria andThirdPartTemplateNotEqualTo(Boolean value) {
addCriterion("third_part_template <>", value, "thirdPartTemplate");
return (Criteria) this;
}
public Criteria andThirdPartTemplateGreaterThan(Boolean value) {
addCriterion("third_part_template >", value, "thirdPartTemplate");
return (Criteria) this;
}
public Criteria andThirdPartTemplateGreaterThanOrEqualTo(Boolean value) {
addCriterion("third_part_template >=", value, "thirdPartTemplate");
return (Criteria) this;
}
public Criteria andThirdPartTemplateLessThan(Boolean value) {
addCriterion("third_part_template <", value, "thirdPartTemplate");
return (Criteria) this;
}
public Criteria andThirdPartTemplateLessThanOrEqualTo(Boolean value) {
addCriterion("third_part_template <=", value, "thirdPartTemplate");
return (Criteria) this;
}
public Criteria andThirdPartTemplateIn(List<Boolean> values) {
addCriterion("third_part_template in", values, "thirdPartTemplate");
return (Criteria) this;
}
public Criteria andThirdPartTemplateNotIn(List<Boolean> values) {
addCriterion("third_part_template not in", values, "thirdPartTemplate");
return (Criteria) this;
}
public Criteria andThirdPartTemplateBetween(Boolean value1, Boolean value2) {
addCriterion("third_part_template between", value1, value2, "thirdPartTemplate");
return (Criteria) this;
}
public Criteria andThirdPartTemplateNotBetween(Boolean value1, Boolean value2) {
addCriterion("third_part_template not between", value1, value2, "thirdPartTemplate");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {
@ -1527,4 +1657,4 @@ public class ProjectExample {
this(condition, value, secondValue, null);
}
}
}
}

View File

@ -22,6 +22,8 @@
<result column="mock_tcp_port" jdbcType="INTEGER" property="mockTcpPort" />
<result column="is_mock_tcp_open" jdbcType="BIT" property="isMockTcpOpen" />
<result column="azure_filter_id" jdbcType="VARCHAR" property="azureFilterId" />
<result column="platform" jdbcType="VARCHAR" property="platform" />
<result column="third_part_template" jdbcType="BIT" property="thirdPartTemplate" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -82,9 +84,10 @@
</where>
</sql>
<sql id="Base_Column_List">
id, workspace_id, `name`, description, create_time, update_time, tapd_id, jira_key,
zentao_id, azure_devops_id, `repeatable`, case_template_id, issue_template_id, custom_num,
scenario_custom_num, create_user, system_id, mock_tcp_port, is_mock_tcp_open, azure_filter_id
id, workspace_id, `name`, description, create_time, update_time, tapd_id, jira_key,
zentao_id, azure_devops_id, `repeatable`, case_template_id, issue_template_id, custom_num,
scenario_custom_num, create_user, system_id, mock_tcp_port, is_mock_tcp_open, azure_filter_id,
platform, third_part_template
</sql>
<select id="selectByExample" parameterType="io.metersphere.base.domain.ProjectExample" resultMap="BaseResultMap">
select
@ -101,7 +104,7 @@
</if>
</select>
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap">
select
select
<include refid="Base_Column_List" />
from project
where id = #{id,jdbcType=VARCHAR}
@ -117,20 +120,22 @@
</if>
</delete>
<insert id="insert" parameterType="io.metersphere.base.domain.Project">
insert into project (id, workspace_id, `name`,
description, create_time, update_time,
tapd_id, jira_key, zentao_id,
azure_devops_id, `repeatable`, case_template_id,
issue_template_id, custom_num, scenario_custom_num,
create_user, system_id, mock_tcp_port,
is_mock_tcp_open, azure_filter_id)
values (#{id,jdbcType=VARCHAR}, #{workspaceId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
#{description,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
#{tapdId,jdbcType=VARCHAR}, #{jiraKey,jdbcType=VARCHAR}, #{zentaoId,jdbcType=VARCHAR},
#{azureDevopsId,jdbcType=VARCHAR}, #{repeatable,jdbcType=BIT}, #{caseTemplateId,jdbcType=VARCHAR},
#{issueTemplateId,jdbcType=VARCHAR}, #{customNum,jdbcType=BIT}, #{scenarioCustomNum,jdbcType=BIT},
#{createUser,jdbcType=VARCHAR}, #{systemId,jdbcType=VARCHAR}, #{mockTcpPort,jdbcType=INTEGER},
#{isMockTcpOpen,jdbcType=BIT}, #{azureFilterId,jdbcType=VARCHAR})
insert into project (id, workspace_id, `name`,
description, create_time, update_time,
tapd_id, jira_key, zentao_id,
azure_devops_id, `repeatable`, case_template_id,
issue_template_id, custom_num, scenario_custom_num,
create_user, system_id, mock_tcp_port,
is_mock_tcp_open, azure_filter_id, platform,
third_part_template)
values (#{id,jdbcType=VARCHAR}, #{workspaceId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
#{description,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
#{tapdId,jdbcType=VARCHAR}, #{jiraKey,jdbcType=VARCHAR}, #{zentaoId,jdbcType=VARCHAR},
#{azureDevopsId,jdbcType=VARCHAR}, #{repeatable,jdbcType=BIT}, #{caseTemplateId,jdbcType=VARCHAR},
#{issueTemplateId,jdbcType=VARCHAR}, #{customNum,jdbcType=BIT}, #{scenarioCustomNum,jdbcType=BIT},
#{createUser,jdbcType=VARCHAR}, #{systemId,jdbcType=VARCHAR}, #{mockTcpPort,jdbcType=INTEGER},
#{isMockTcpOpen,jdbcType=BIT}, #{azureFilterId,jdbcType=VARCHAR}, #{platform,jdbcType=VARCHAR},
#{thirdPartTemplate,jdbcType=BIT})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.Project">
insert into project
@ -195,6 +200,12 @@
<if test="azureFilterId != null">
azure_filter_id,
</if>
<if test="platform != null">
platform,
</if>
<if test="thirdPartTemplate != null">
third_part_template,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -257,6 +268,12 @@
<if test="azureFilterId != null">
#{azureFilterId,jdbcType=VARCHAR},
</if>
<if test="platform != null">
#{platform,jdbcType=VARCHAR},
</if>
<if test="thirdPartTemplate != null">
#{thirdPartTemplate,jdbcType=BIT},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.ProjectExample" resultType="java.lang.Long">
@ -328,6 +345,12 @@
<if test="record.azureFilterId != null">
azure_filter_id = #{record.azureFilterId,jdbcType=VARCHAR},
</if>
<if test="record.platform != null">
platform = #{record.platform,jdbcType=VARCHAR},
</if>
<if test="record.thirdPartTemplate != null">
third_part_template = #{record.thirdPartTemplate,jdbcType=BIT},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -354,7 +377,9 @@
system_id = #{record.systemId,jdbcType=VARCHAR},
mock_tcp_port = #{record.mockTcpPort,jdbcType=INTEGER},
is_mock_tcp_open = #{record.isMockTcpOpen,jdbcType=BIT},
azure_filter_id = #{record.azureFilterId,jdbcType=VARCHAR}
azure_filter_id = #{record.azureFilterId,jdbcType=VARCHAR},
platform = #{record.platform,jdbcType=VARCHAR},
third_part_template = #{record.thirdPartTemplate,jdbcType=BIT}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -419,6 +444,12 @@
<if test="azureFilterId != null">
azure_filter_id = #{azureFilterId,jdbcType=VARCHAR},
</if>
<if test="platform != null">
platform = #{platform,jdbcType=VARCHAR},
</if>
<if test="thirdPartTemplate != null">
third_part_template = #{thirdPartTemplate,jdbcType=BIT},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
@ -442,7 +473,9 @@
system_id = #{systemId,jdbcType=VARCHAR},
mock_tcp_port = #{mockTcpPort,jdbcType=INTEGER},
is_mock_tcp_open = #{isMockTcpOpen,jdbcType=BIT},
azure_filter_id = #{azureFilterId,jdbcType=VARCHAR}
azure_filter_id = #{azureFilterId,jdbcType=VARCHAR},
platform = #{platform,jdbcType=VARCHAR},
third_part_template = #{thirdPartTemplate,jdbcType=BIT}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>
</mapper>

View File

@ -59,10 +59,6 @@
from issues
left join
test_case_issues on issues.id = test_case_issues.issues_id
<if test="request.projectId != null||request.workspaceId != null">
left join
project on issues.project_id = project.id
</if>
<include refid="queryWhereCondition"/>
and (test_case_issues.test_case_id is null or test_case_issues.test_case_id != #{request.caseId})
and (issues.platform_status != 'delete' or issues.platform_status is NULL)

View File

@ -52,7 +52,8 @@
user.name AS createUserName,
p.mock_tcp_port AS mockTcpPort,
p.is_mock_tcp_open AS isMockTcpOpen,
p.scenario_custom_num
p.scenario_custom_num,
p.platform, p.third_part_template
FROM project p
JOIN workspace w ON p.workspace_id = w.id
LEFT JOIN user ON user.id = p.create_user

View File

@ -12,6 +12,7 @@ import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.*;
import io.metersphere.controller.request.IntegrationRequest;
import io.metersphere.dto.CustomFieldItemDTO;
import io.metersphere.dto.IssueTemplateDao;
import io.metersphere.dto.UserDTO;
import io.metersphere.service.*;
import io.metersphere.track.request.testcase.IssuesRequest;
@ -363,7 +364,6 @@ public abstract class AbstractIssuePlatform implements IssuesPlatform {
});
}
protected String syncIssueCustomField(String customFieldsStr, JSONObject issue) {
List<CustomFieldItemDTO> customFields = CustomFieldService.getCustomFields(customFieldsStr);
customFields.forEach(item -> {
@ -395,6 +395,9 @@ public abstract class AbstractIssuePlatform implements IssuesPlatform {
@Override
public void syncAllIssues(Project project) {}
@Override
public IssueTemplateDao getThirdPartTemplate() {return null;}
protected List<IssuesWithBLOBs> getIssuesByPlatformIds(List<String> platformIds) {
IssuesService issuesService = CommonBeanFactory.getBean(IssuesService.class);
return issuesService.getIssuesByPlatformIds(platformIds, projectId);

View File

@ -3,6 +3,7 @@ package io.metersphere.track.issue;
import io.metersphere.base.domain.IssuesDao;
import io.metersphere.base.domain.IssuesWithBLOBs;
import io.metersphere.base.domain.Project;
import io.metersphere.dto.IssueTemplateDao;
import io.metersphere.dto.UserDTO;
import io.metersphere.track.dto.DemandDTO;
import io.metersphere.track.issue.domain.PlatformUser;
@ -71,4 +72,10 @@ public interface IssuesPlatform {
* @param project
*/
void syncAllIssues(Project project);
/**
* 获取第三方平台缺陷模板
* @return
*/
IssueTemplateDao getThirdPartTemplate();
}

View File

@ -20,6 +20,7 @@ import io.metersphere.track.issue.domain.jira.JiraConfig;
import io.metersphere.track.issue.domain.jira.JiraIssue;
import io.metersphere.track.request.testcase.IssuesRequest;
import io.metersphere.track.request.testcase.IssuesUpdateRequest;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
@ -148,9 +149,7 @@ public class JiraPlatform extends AbstractIssuePlatform {
List<File> imageFiles = getImageFiles(issuesRequest.getDescription());
imageFiles.forEach(img -> {
jiraClientV2.uploadAttachment(result.getKey(), img);
});
imageFiles.forEach(img -> jiraClientV2.uploadAttachment(result.getKey(), img));
String status = getStatus(issues.getFields());
issuesRequest.setPlatformStatus(status);
@ -184,50 +183,78 @@ public class JiraPlatform extends AbstractIssuePlatform {
JSONObject issuetype = new JSONObject();
issuetype.put("name", issuetypeStr);
fields.put("summary", issuesRequest.getTitle());
// fields.put("description", new JiraIssueDescription(desc));
fields.put("description", desc);
fields.put("issuetype", issuetype);
JSONObject addJiraIssueParam = new JSONObject();
addJiraIssueParam.put("fields", fields);
if (issuesRequest.isThirdPartPlatform()) {
parseCustomFiled(issuesRequest, fields);
issuesRequest.setTitle(fields.getString("summary"));
} else {
fields.put("summary", issuesRequest.getTitle());
fields.put("description", desc);
parseCustomFiled(issuesRequest, fields);
}
return addJiraIssueParam;
}
private void parseCustomFiled(IssuesUpdateRequest issuesRequest, JSONObject fields) {
List<CustomFieldItemDTO> customFields = CustomFieldService.getCustomFields(issuesRequest.getCustomFields());
customFields.forEach(item -> {
String fieldName = item.getCustomData();
if (StringUtils.isNotBlank(fieldName)) {
if (item.getValue() != null) {
if (StringUtils.isNotBlank(item.getType()) &&
StringUtils.equalsAny(item.getType(), "select", "radio", "member")) {
JSONObject param = new JSONObject();
if (fieldName.equals("assignee") || fieldName.equals("reporter")) {
param.put("name", item.getValue());
if (StringUtils.isNotBlank(item.getType())) {
if (StringUtils.equalsAny(item.getType(), "select", "radio", "member")) {
JSONObject param = new JSONObject();
if (fieldName.equals("assignee") || fieldName.equals("reporter")) {
if (issuesRequest.isThirdPartPlatform()) {
param.put("id", item.getValue());
} else {
param.put("name", item.getValue());
}
} else {
param.put("id", item.getValue());
}
fields.put(fieldName, param);
} else if (StringUtils.equalsAny(item.getType(), "multipleSelect", "checkbox", "multipleMember")) {
JSONArray attrs = new JSONArray();
if (item.getValue() != null) {
JSONArray values = (JSONArray)item.getValue();
values.forEach(v -> {
JSONObject param = new JSONObject();
param.put("id", v);
attrs.add(param);
});
}
fields.put(fieldName, attrs);
} else if (StringUtils.equalsAny(item.getType(), "cascadingSelect")) {
if (item.getValue() != null) {
JSONArray values = (JSONArray)item.getValue();
JSONObject attr = new JSONObject();
if (CollectionUtils.isNotEmpty(values)) {
if (values.size() > 0) {
attr.put("id", values.get(0));
}
if (values.size() > 1) {
JSONObject param = new JSONObject();
param.put("id", values.get(1));
attr.put("child", param);
}
}
fields.put(fieldName, attr);
}
} else {
param.put("id", item.getValue());
fields.put(fieldName, item.getValue());
}
fields.put(fieldName, param);
} else if (StringUtils.isNotBlank(item.getType()) &&
StringUtils.equalsAny(item.getType(), "multipleSelect", "checkbox", "multipleMember")) {
JSONArray attrs = new JSONArray();
if (item.getValue() != null) {
JSONArray values = (JSONArray)item.getValue();
values.forEach(v -> {
JSONObject param = new JSONObject();
param.put("id", v);
attrs.add(param);
});
}
fields.put(fieldName, attrs);
} else {
fields.put(fieldName, item.getValue());
}
}
}
});
return addJiraIssueParam;
}
@Override

View File

@ -4,11 +4,7 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.track.issue.domain.jira.JiraAddIssueResponse;
import io.metersphere.track.issue.domain.jira.JiraConfig;
import io.metersphere.track.issue.domain.jira.JiraField;
import io.metersphere.track.issue.domain.jira.JiraIssue;
import io.metersphere.track.issue.domain.jira.JiraIssueListResponse;
import io.metersphere.track.issue.domain.jira.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
@ -16,7 +12,9 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public abstract class JiraAbstractClient extends BaseClient {
@ -35,6 +33,34 @@ public abstract class JiraAbstractClient extends BaseClient {
return (JiraIssue) getResultForObject(JiraIssue.class, responseEntity);
}
public Map<String, JiraCreateMetadataResponse.Field> getCreateMetadata(String projectKey, String issueType) {
String url = getBaseUrl() + "/issue/createmeta?projectKeys={1}&issuetypeNames={2}&expand=projects.issuetypes.fields";
ResponseEntity<String> response = null;
try {
response = restTemplate.exchange(url, HttpMethod.GET, getAuthHttpEntity(), String.class, projectKey, issueType);
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(e.getMessage());
}
Map<String, JiraCreateMetadataResponse.Field> fields = ((JiraCreateMetadataResponse) getResultForObject(JiraCreateMetadataResponse.class, response))
.getProjects().get(0).getIssuetypes().get(0).getFields();
fields.remove("project");
fields.remove("issuetype");
return fields;
}
public List<JiraUser> getAssignableUser(String projectKey) {
String url = getBaseUrl() + "/user/assignable/search?project={1}";
ResponseEntity<String> response = null;
try {
response = restTemplate.exchange(url, HttpMethod.GET, getAuthHttpEntity(), String.class, projectKey);
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(e.getMessage());
}
return (List<JiraUser>) getResultForList(JiraUser.class, response);
}
public JSONArray getDemands(String projectKey, String issueType, int startAt, int maxResults) {
String jql = getBaseUrl() + "/search?jql=project=" + projectKey + "+AND+issuetype=" + issueType
+ "&maxResults=" + maxResults + "&startAt=" + startAt + "&fields=summary,issuetype";

View File

@ -0,0 +1,61 @@
package io.metersphere.track.issue.domain.jira;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.Map;
@Setter
@Getter
public class JiraCreateMetadataResponse {
private List<Projects> projects;
@Setter
@Getter
public static class Projects {
private List<Issuetypes> issuetypes;
}
@Setter
@Getter
public static class Issuetypes {
private Map<String, Field> fields;
}
@Setter
@Getter
public static class Field {
private boolean required;
private Schema schema;
private String name;
private String key;
private String autoCompleteUrl;
private boolean hasDefaultValue;
private Object defaultValue;
private List<AllowedValues> allowedValues;
}
@Setter
@Getter
public static class Schema {
private String type;
private String items;
private String custom;
private int customId;
}
@Setter
@Getter
public static class AllowedValues {
private String self;
private String id;
private String description;
private String name;
private String value;
private boolean subtask;
private int avatarId;
private int hierarchyLevel;
private List<AllowedValues> children;
}
}

View File

@ -0,0 +1,13 @@
package io.metersphere.track.issue.domain.jira;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class JiraUser {
private String accountId;
private String displayName;
private String emailAddress;
private Boolean active;
}

View File

@ -25,5 +25,7 @@ public class IssuesUpdateRequest extends IssuesWithBLOBs {
private List<String> zentaoBuilds;
private List<String> testCaseIds;
private boolean thirdPartPlatform;
private List<String> follows;
}

@ -1 +1 @@
Subproject commit 269d23d5004ee49a9e82a977027ca72757d3056a
Subproject commit a99681fe94107d42ff340f4e8dcdca95195cf884

View File

@ -1,2 +1,11 @@
-- 新增字段
ALTER TABLE `swagger_url_project` ADD COLUMN `config` longtext COMMENT '鉴权配置信息' AFTER `mode_id`;
ALTER TABLE `swagger_url_project` ADD COLUMN `config` longtext COMMENT '鉴权配置信息' AFTER `mode_id`;
-- 第三方平台模板
ALTER TABLE project ADD platform varchar(20) DEFAULT 'Local' NOT NULL COMMENT '项目使用哪个平台的模板';
ALTER TABLE project ADD third_part_template tinyint(1) DEFAULT 0 NULL COMMENT '是否使用第三方平台缺陷模板';
-- 处理历史数据
UPDATE issue_template SET platform = 'Local' WHERE platform = 'metersphere';
UPDATE project p JOIN issue_template it on p.issue_template_id = it.id SET p.platform = it.platform;
UPDATE custom_field SET `type` = 'date' WHERE `type` = 'data';

View File

@ -7,11 +7,23 @@
<el-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item v-if="platformOptions.length > 1" :label-width="labelWidth" :label="$t('集成第三方平台')" prop="platform">
<el-select filterable v-model="form.platform">
<el-option v-for="item in platformOptions" :key="item.value" :label="item.text" :value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item :label-width="labelWidth" :label="$t('workspace.case_template_manage')" prop="caseTemplateId">
<template-select :data="form" scene="API_CASE" prop="caseTemplateId" ref="caseTemplate"/>
</el-form-item>
<el-form-item :label-width="labelWidth" :label="$t('workspace.issue_template_manage')" prop="issueTemplateId">
<template-select :data="form" scene="ISSUE" prop="issueTemplateId" ref="issueTemplate"/>
<el-form-item v-if="xpackEable" :label-width="labelWidth" :label="$t('使用第三方平台模板')" prop="scenarioCustomNum">
<el-switch v-model="form.thirdPartTemplate"></el-switch>
</el-form-item>
<el-form-item v-if="!xpackEable || !form.thirdPartTemplate" :label-width="labelWidth" :label="$t('workspace.issue_template_manage')" prop="issueTemplateId">
<template-select :platform="form.platform" :data="form" scene="ISSUE" prop="issueTemplateId" ref="issueTemplate"/>
</el-form-item>
<el-form-item :label-width="labelWidth" label="TCP Mock Port">
@ -79,12 +91,12 @@
import {
getCurrentProjectID,
getCurrentUser, getCurrentUserId,
getCurrentWorkspaceId,
getCurrentWorkspaceId, hasLicense,
listenGoBack,
removeGoBackListener
} from "@/common/js/utils";
import {PROJECT_ID} from "@/common/js/constants";
import {AZURE_DEVOPS, JIRA, PROJECT_ID, TAPD, ZEN_TAO} from "@/common/js/constants";
import {PROJECT_CONFIGS} from "@/business/components/common/components/search/search-components";
import MsInstructionsIcon from "@/business/components/common/components/MsInstructionsIcon";
import TemplateSelect from "@/business/components/settings/workspace/template/TemplateSelect";
@ -102,9 +114,10 @@ import MsTableOperator from "@/business/components/common/components/MsTableOper
import MsTablePagination from "@/business/components/common/pagination/TablePagination";
import MsTableHeader from "@/business/components/common/components/MsTableHeader";
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
import {ISSUE_PLATFORM_OPTION} from "@/common/js/table-constants";
export default {
name: "MsProject",
name: "EditProject",
components: {
MsInstructionsIcon,
TemplateSelect,
@ -126,10 +139,6 @@ export default {
title: this.$t('project.create'),
condition: {components: PROJECT_CONFIGS},
items: [],
tapd: false,
jira: false,
zentao: false,
azuredevops: false,
form: {},
currentPage: 1,
pageSize: 10,
@ -147,7 +156,9 @@ export default {
// issueTemplateId: [{required: true}],
},
screenHeight: 'calc(100vh - 195px)',
labelWidth: '150px'
labelWidth: '150px',
platformOptions: [],
xpackEable: false
};
},
props: {
@ -161,11 +172,24 @@ export default {
this.create();
this.$router.replace('/setting/project/all');
}
this.xpackEable = hasLicense();
},
computed: {
currentUser: () => {
return getCurrentUser();
}
},
tapd() {
return this.form.platform === TAPD && this.platformOptions.map(i => i.value).indexOf(TAPD) > -1;
},
jira() {
return this.form.platform === JIRA && this.platformOptions.map(i => i.value).indexOf(JIRA) > -1;
},
zentao() {
return this.form.platform === ZEN_TAO && this.platformOptions.map(i => i.value).indexOf(ZEN_TAO) > -1;
},
azuredevops() {
return this.form.platform === AZURE_DEVOPS && this.platformOptions.map(i => i.value).indexOf(AZURE_DEVOPS) > -1;
},
},
inject: [
'reload'
@ -187,24 +211,32 @@ export default {
this.getOptions();
this.createVisible = true;
listenGoBack(this.handleClose);
this.form = Object.assign({}, row);
if (row) {
this.form = Object.assign({}, row);
} else {
this.form = {};
}
this.platformOptions = [];
this.platformOptions.push(...ISSUE_PLATFORM_OPTION);
this.$get("/service/integration/all/" + getCurrentUser().lastWorkspaceId, response => {
let data = response.data;
let platforms = data.map(d => d.platform);
if (platforms.indexOf("Tapd") !== -1) {
this.tapd = true;
}
if (platforms.indexOf("Jira") !== -1) {
this.jira = true;
}
if (platforms.indexOf("Zentao") !== -1) {
this.zentao = true;
}
if (platforms.indexOf("AzureDevops") !== -1) {
this.azuredevops = true;
}
this.filterPlatformOptions(platforms, TAPD);
this.filterPlatformOptions(platforms, JIRA);
this.filterPlatformOptions(platforms, ZEN_TAO);
this.filterPlatformOptions(platforms, AZURE_DEVOPS);
});
},
filterPlatformOptions(platforms, platform) {
if (platforms.indexOf(platform) === -1) {
for (let i = 0; i < this.platformOptions.length; i++) {
if (this.platformOptions[i].value === platform) {
this.platformOptions.splice(1, i);
break;
}
}
}
},
submit(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
@ -254,10 +286,6 @@ export default {
handleClose() {
removeGoBackListener(this.handleClose);
this.createVisible = false;
this.tapd = false;
this.jira = false;
this.zentao = false;
this.azuredevops = false;
},
chengeMockTcpSwitch(value){
if(value && this.form.mockTcpPort === 0){

View File

@ -93,77 +93,7 @@
:total="total"/>
</el-card>
<el-dialog :close-on-click-modal="false" :title="title" :visible.sync="createVisible" destroy-on-close
@close="handleClose">
<el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="80px" size="small">
<el-form-item :label-width="labelWidth" :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item :label-width="labelWidth" :label="$t('用例模板')" prop="caseTemplateId">
<template-select :data="form" scene="API_CASE" prop="caseTemplateId" ref="caseTemplate"/>
</el-form-item>
<el-form-item :label-width="labelWidth" :label="$t('缺陷模板')" prop="issueTemplateId">
<template-select :data="form" scene="ISSUE" prop="issueTemplateId" ref="issueTemplate"/>
</el-form-item>
<el-form-item :label-width="labelWidth" label="TCP Mock Port">
<el-input-number v-model="form.mockTcpPort" :controls="false"
style="width: 37%;margin-right: 30px"></el-input-number>
<el-switch v-model="form.isMockTcpOpen" @change="chengeMockTcpSwitch"></el-switch>
</el-form-item>
<el-form-item :label-width="labelWidth" :label="$t('commons.description')" prop="description">
<el-input :autosize="{ minRows: 2, maxRows: 4}" type="textarea" v-model="form.description" ></el-input>
</el-form-item>
<el-form-item :label-width="labelWidth" :label="$t('project.tapd_id')" v-if="tapd">
<el-input v-model="form.tapdId" autocomplete="off" ></el-input>
</el-form-item>
<el-form-item :label-width="labelWidth" :label="$t('project.jira_key')" v-if="jira">
<el-input v-model="form.jiraKey" autocomplete="off" />
<ms-instructions-icon effect="light">
<template>
<img class="jira-image" src="../../../../assets/jira-key.png"/>
</template>
</ms-instructions-icon>
</el-form-item>
<el-form-item :label-width="labelWidth" :label="$t('project.zentao_id')" v-if="zentao">
<el-input v-model="form.zentaoId" autocomplete="off" ></el-input>
<ms-instructions-icon effect="light">
<template>
禅道流程产品-项目 | 产品-迭代 | 产品-冲刺 | 项目-迭代 | 项目-冲刺 <br/><br/>
根据 "后台 -> 自定义 -> 流程" 查看对应流程根据流程填写ID <br/><br/>
产品-项目 | 产品-迭代 | 产品-冲刺 需要填写产品ID <br/><br/>
项目-迭代 | 项目-冲刺 需要填写项目ID
</template>
</ms-instructions-icon>
</el-form-item>
<el-form-item :label-width="labelWidth" :label="$t('project.azureDevops_id')" v-if="azuredevops">
<el-input v-model="form.azureDevopsId" autocomplete="off" ></el-input>
</el-form-item>
<el-form-item :label-width="labelWidth" :label="$t('project.azureDevops_filter_id')" v-if="azuredevops">
<el-input v-model="form.azureFilterId" autocomplete="off"/>
<ms-instructions-icon content="非必填项,用例关联需求时,可以只筛选出,所填的 workItem 下的选项" effect="light"/>
</el-form-item>
<el-form-item :label-width="labelWidth" :label="$t('project.repeatable')" prop="repeatable">
<el-switch v-model="form.repeatable"></el-switch>
</el-form-item>
<el-form-item :label-width="labelWidth" :label="$t('project.test_case_custom_id')" prop="customNum">
<el-switch v-model="form.customNum"></el-switch>
</el-form-item>
<el-form-item :label-width="labelWidth" :label="$t('project.scenario_custom_id')" prop="scenarioCustomNum">
<el-switch v-model="form.scenarioCustomNum"></el-switch>
</el-form-item>
</el-form>
<template v-slot:footer>
<div class="dialog-footer">
<ms-dialog-footer
@cancel="createVisible = false"
@confirm="submit('form')"/>
</div>
</template>
</el-dialog>
<edit-project ref="editProject"/>
<el-dialog :close-on-click-modal="false" :visible.sync="memberVisible" width="70%" :destroy-on-close="true"
@close="close"
@ -280,10 +210,12 @@ import {PROJECT_CONFIGS} from "@/business/components/common/components/search/se
import MsRolesTag from "@/business/components/common/components/MsRolesTag";
import AddMember from "@/business/components/settings/common/AddMember";
import MsInstructionsIcon from "@/business/components/common/components/MsInstructionsIcon";
import EditProject from "@/business/components/project/menu/EditProject";
export default {
name: "MsProject",
components: {
EditProject,
MsInstructionsIcon,
TemplateSelect,
MsResourceFiles,
@ -302,7 +234,6 @@ export default {
],
data() {
return {
createVisible: false,
updateVisible: false,
dialogMemberVisible: false,
result: {},
@ -310,10 +241,6 @@ export default {
title: this.$t('project.create'),
condition: {components: PROJECT_CONFIGS},
items: [],
tapd: false,
jira: false,
zentao: false,
azuredevops: false,
form: {},
currentPage: 1,
pageSize: 10,
@ -371,9 +298,6 @@ export default {
return getCurrentWorkspaceId();
}
},
destroyed() {
this.createVisible = false;
},
methods: {
jumpPage(row) {
this.currentWorkspaceRow = row;
@ -383,7 +307,6 @@ export default {
projectId: row.id
};
this.wsId = row.id;
let path = "/user/project/member/list";
this.result = this.$post("/user/project/member/list", param, res => {
let data = res.data;
this.memberLineData = data;
@ -398,7 +321,6 @@ export default {
},
getMaintainerOptions() {
let workspaceId = getCurrentWorkspaceId();
this.$post('/user/project/member/tester/list', {projectId: getCurrentProjectID()}, response => {
this.userFilters = response.data.map(u => {
return {text: u.name, value: u.id};
@ -407,73 +329,17 @@ export default {
},
create() {
let workspaceId = getCurrentWorkspaceId();
this.getOptions();
if (!workspaceId) {
this.$warning(this.$t('project.please_choose_workspace'));
return false;
}
this.title = this.$t('project.create');
// listenGoBack(this.handleClose);
this.createVisible = true;
this.form = {};
},
getOptions() {
if (this.$refs.issueTemplate) {
this.$refs.issueTemplate.getTemplateOptions();
}
if (this.$refs.caseTemplate) {
this.$refs.caseTemplate.getTemplateOptions();
}
this.$refs.editProject.edit();
},
edit(row) {
this.title = this.$t('project.edit');
this.getOptions();
this.createVisible = true;
listenGoBack(this.handleClose);
this.form = Object.assign({}, row);
this.$get("/service/integration/all/" + getCurrentWorkspaceId(), response => {
let data = response.data;
let platforms = data.map(d => d.platform);
if (platforms.indexOf("Tapd") !== -1) {
this.tapd = true;
}
if (platforms.indexOf("Jira") !== -1) {
this.jira = true;
}
if (platforms.indexOf("Zentao") !== -1) {
this.zentao = true;
}
if (platforms.indexOf("AzureDevops") !== -1) {
this.azuredevops = true;
}
});
},
submit(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
let saveType = "add";
if (this.form.id) {
saveType = "update";
}
var protocol = document.location.protocol;
protocol = protocol.substring(0, protocol.indexOf(":"));
this.form.protocal = protocol;
this.form.workspaceId = getCurrentWorkspaceId();
this.form.createUser = getCurrentUserId();
this.result = this.$post("/project/" + saveType, this.form, () => {
this.createVisible = false;
Message.success(this.$t('commons.save_success'));
if (saveType === 'add') {
this.reloadTopMenus();
} else {
this.list();
}
});
} else {
return false;
}
});
this.$refs.editProject.edit(row);
},
openJarConfig() {
this.$refs.jarConfig.open();
@ -507,11 +373,6 @@ export default {
},
handleClose() {
removeGoBackListener(this.handleClose);
this.createVisible = false;
this.tapd = false;
this.jira = false;
this.zentao = false;
this.azuredevops = false;
},
search() {
this.list();

View File

@ -14,6 +14,14 @@
</el-option>
</el-select>
<el-cascader
v-else-if="data.type === 'cascadingSelect'"
expand-trigger="hover"
@change="handleChange"
:options="data.options"
v-model="data[prop]">
</el-cascader>
<el-input
v-else-if="data.type === 'textarea'"
type="textarea"
@ -52,21 +60,21 @@
v-else-if="data.type === 'int'"
v-model="data[prop]"
:disabled="disabled"
@change="handleChange"></el-input-number>
@change="handleChange"/>
<el-input-number
v-else-if="data.type === 'float'"
:disabled="disabled"
@change="handleChange"
v-model="data[prop]" :precision="2" :step="0.1"></el-input-number>
v-model="data[prop]" :precision="2" :step="0.1"/>
<el-date-picker
class="custom-with"
@change="handleChange"
v-else-if="data.type === 'data'"
v-else-if="data.type === 'date' || data.type === 'datetime'"
:disabled="disabled"
v-model="data[prop]"
type="date"
:type="data.type === 'date' ? 'date' : 'datetime'"
:placeholder="$t('commons.select_date')">
</el-date-picker>
@ -84,10 +92,16 @@
</el-select>
<ms-input-tag v-else-if="data.type === 'multipleInput'"
@input="handleChange"
:read-only="disabled" :currentScenario="data" :prop="prop"/>
<ms-mark-down-text v-else-if="data.type === 'richText'"
:prop="prop"
@change="handleChange"
:data="data" :disabled="disabled"/>
<el-input class="custom-with"
@change="handleChange"
@input="handleChange"
:disabled="disabled"
v-else v-model="data[prop]"/>
@ -97,11 +111,12 @@
<script>
import MsTableColumn from "@/business/components/common/components/table/MsTableColumn";
import {getCurrentProjectID, getCurrentWorkspaceId} from "@/common/js/utils";
import {getCurrentProjectID} from "@/common/js/utils";
import MsInputTag from "@/business/components/api/automation/scenario/MsInputTag";
import MsMarkDownText from "@/business/components/track/case/components/MsMarkDownText";
export default {
name: "CustomFiledComponent",
components: {MsInputTag, MsTableColumn},
components: {MsMarkDownText, MsInputTag, MsTableColumn},
props: [
'data',
'prop',
@ -113,6 +128,9 @@ export default {
memberOptions: [],
};
},
created() {
this.initOption(this.data.options);
},
mounted() {
this.$post('/user/project/member/tester/list', {projectId: getCurrentProjectID()}, response => {
this.memberOptions = response.data;
@ -125,7 +143,15 @@ export default {
handleChange() {
if (this.form) {
this.$set(this.form, this.data.name, this.data[this.prop]);
this.$emit('reload');
}
},
initOption(options) {
if (options) {
options.forEach(i => {
i.label = i.text;
this.$set(i, 'label', i.text);
this.initOption(i.children);
});
}
}
}

View File

@ -47,6 +47,7 @@ import CustomFieldFormList from "@/business/components/settings/workspace/templa
import CustomFieldRelateList from "@/business/components/settings/workspace/template/CustomFieldRelateList";
import FieldTemplateEdit from "@/business/components/settings/workspace/template/FieldTemplateEdit";
import FormRIchTextItem from "@/business/components/track/case/components/FormRichTextItem";
import {LOCAL} from "@/common/js/constants";
export default {
name: "IssueTemplateEdit",
@ -64,7 +65,7 @@ export default {
showDialog: false,
form: {
name: "",
platform: 'metersphere',
platform: LOCAL,
description: '',
title: '',
content: '',
@ -106,7 +107,7 @@ export default {
this.form = {
id: "",
name: "",
platform: 'metersphere',
platform: LOCAL,
description: '',
title: '',
content: '',

View File

@ -105,7 +105,7 @@ export default {
currentPage: 1,
result: {},
issuePlatformMap:{
metersphere: 'Metersphere',
Local: 'Metersphere',
Jira: 'JIRA',
Tapd: 'Tapd',
Zentao: '禅道',

View File

@ -1,7 +1,7 @@
<template>
<el-select filterable v-model="data[prop]">
<el-option
v-for="(item, index) in templateOptions"
v-for="(item, index) in templateFilterOptions"
:key="index"
:label="item.name"
:value="item.id">
@ -17,6 +17,7 @@ export default {
props: {
scene: String,
prop: String,
platform: String,
data: {
type: Object,
default() {
@ -29,12 +30,18 @@ export default {
},
data() {
return {
templateOptions: []
templateOptions: [],
templateFilterOptions: [],
};
},
mounted() {
this.getTemplateOptions();
},
watch: {
platform() {
this.filter();
}
},
methods: {
getTemplateOptions() {
let url = 'field/template/case/option/';
@ -43,6 +50,7 @@ export default {
}
this.$get(url + getCurrentWorkspaceId(), (response) => {
this.templateOptions = response.data;
this.templateFilterOptions = this.templateOptions;
if (!this.data[this.prop]) {
for (let item of this.templateOptions) {
if (this.scene !== 'ISSUE') {
@ -59,7 +67,25 @@ export default {
}
}
this.filter();
});
},
filter() {
if (this.platform) {
let hasTemplate = false;
this.templateFilterOptions = [];
this.templateOptions.forEach(i => {
if (i.platform === this.platform) {
this.templateFilterOptions.push(i);
if (i.id === this.data[this.prop])
hasTemplate = true;
}
});
if (!hasTemplate)
this.data[this.prop] = null;
} else {
this.templateFilterOptions = this.templateOptions;
}
}
}
};

View File

@ -1,138 +1,17 @@
<template>
<el-form-item v-loading="result.loading" :disable="true" :label="title" :prop="prop" :label-width="labelWidth">
<mavon-editor :id="id" v-if="active" :editable="!disabled" @imgAdd="imgAdd" :default-open="defaultOpen" class="mavon-editor"
:xss-options="xssOptions"
:subfield="false" :toolbars="toolbars" :language="language" :toolbarsFlag="disabled ? false : true" @imgDel="imgDel" v-model="data[prop]" ref="md"/>
<el-form-item :disable="true" :label="title" :prop="prop" :label-width="labelWidth">
<ms-mark-down-text :prop="prop" :data="data" :disabled="disabled"/>
</el-form-item>
</template>
<script>
import {getCurrentUser, getUUID} from "@/common/js/utils";
import MsMarkDownText from "@/business/components/track/case/components/MsMarkDownText";
export default {
name: "FormRichTextItem",
components: {},
props: ['data', 'title', 'prop', 'disabled', 'labelWidth'],
data() {
return {
result: {loading: false},
id: getUUID(),
xssOptions: {
whiteList: {
img: ["src", "alt", "width", "height"],
},
stripIgnoreTagBody: true
},
defaultOpen: 'preview',
toolbars: {
bold: true, //
italic: true, //
header: true, //
underline: true, // 线
strikethrough: true, // 线
mark: true, //
superscript: true, //
subscript: true, //
quote: true, //
ol: true, //
ul: true, //
link: true, //
imagelink: true, //
code: true, // code
table: true, //
fullscreen: true, //
readmodel: true, //
htmlcode: true, // html
help: true, //
/* 1.3.5 */
undo: true, //
redo: true, //
trash: true, //
save: false, // eventssave
/* 1.4.2 */
navigation: true, //
/* 2.1.8 */
alignleft: true, //
aligncenter: true, //
alignright: true, //
/* 2.2.1 */
subfield: true, //
preview: true, //
}
}
},
computed: {
active() {
if (this.data[this.prop] !== undefined) {
return true;
}
return false;
},
language() {
const user = getCurrentUser();
const language = user.language;
switch (language) {
case 'zh_CN':
return 'zh-CN';
case 'zh_TW':
return 'zh-TW';
case 'en_US':
return 'en';
default:
return 'zh-CN';
}
}
},
mounted() {
//
let el = document.getElementById(this.id);
if (el) {
el.addEventListener('click', () => {
let imagePreview = el.getElementsByClassName('v-note-img-wrapper');
if (imagePreview.length > 0) { //
this.defaultOpen = 'preview';
} else {
this.defaultOpen = null;
}
});
let input = el.getElementsByClassName('auto-textarea-input');
input[0].addEventListener('blur', () => {
this.defaultOpen = 'preview';
});
}
},
methods: {
imgAdd(pos, file){
let param = {
id: getUUID().substring(0, 8)
};
file.prefix = param.id;
this.result.loading = true;
//
param.fileName = file.name.replace("(", "").replace(")", "").replace(" ", "");
this.$fileUpload('/resource/md/upload', file, null, param, () => {
this.$success(this.$t('commons.save_success'));
this.$refs.md.$img2Url(pos, '/resource/md/get/' + param.id + '_' + param.fileName);
this.result.loading = false;
});
this.$emit('imgAdd', file);
},
imgDel(file) {
if (file) {
this.$get('/resource/md/delete/' + file[1].prefix + "_" + file[1].name);
}
},
}
components: {MsMarkDownText},
props: ['data', 'title', 'prop', 'disabled', 'labelWidth']
}
</script>
<style scoped>
.mavon-editor {
min-height: 20px;
}
/deep/ .v-note-wrapper {
position: initial;
}
</style>

View File

@ -73,6 +73,7 @@ import MsTablePagination from "@/business/components/common/pagination/TablePagi
import {getPageInfo} from "@/common/js/tableUtils";
import {getCurrentProjectID} from "@/common/js/utils";
import {getIssueTemplate} from "../../../../../network/custom-field-template";
import {LOCAL} from "@/common/js/constants";
export default {
name: "IssueRelateList",
components: {MsTablePagination, IssueDescriptionTableItem, MsTableColumn, MsTable, MsEditDialog},
@ -95,7 +96,7 @@ export default {
created() {
getIssueTemplate()
.then((template) => {
if (template.platform === 'metersphere') {
if (template.platform === LOCAL) {
this.isThirdPart = false;
} else {
this.isThirdPart = true;

View File

@ -0,0 +1,137 @@
<template>
<mavon-editor :id="id" v-if="active" :editable="!disabled" @imgAdd="imgAdd" :default-open="defaultOpen" class="mavon-editor"
:xss-options="xssOptions"
@change="$emit('change')"
:subfield="false" :toolbars="toolbars" :language="language" :toolbarsFlag="disabled ? false : true" @imgDel="imgDel" v-model="data[prop]" ref="md"/>
</template>
<script>
import {getCurrentUser, getUUID} from "@/common/js/utils";
export default {
name: "MsMarkDownText",
components: {},
props: ['data', 'prop', 'disabled'],
data() {
return {
result: {loading: false},
id: getUUID(),
xssOptions: {
whiteList: {
img: ["src", "alt", "width", "height"],
},
stripIgnoreTagBody: true
},
defaultOpen: 'preview',
toolbars: {
bold: true, //
italic: true, //
header: true, //
underline: true, // 线
strikethrough: true, // 线
mark: true, //
superscript: true, //
subscript: true, //
quote: true, //
ol: true, //
ul: true, //
link: true, //
imagelink: true, //
code: true, // code
table: true, //
fullscreen: true, //
readmodel: true, //
htmlcode: true, // html
help: true, //
/* 1.3.5 */
undo: true, //
redo: true, //
trash: true, //
save: false, // eventssave
/* 1.4.2 */
navigation: true, //
/* 2.1.8 */
alignleft: true, //
aligncenter: true, //
alignright: true, //
/* 2.2.1 */
subfield: true, //
preview: true, //
}
}
},
computed: {
active() {
if (this.data[this.prop] !== undefined) {
return true;
}
return false;
},
language() {
const user = getCurrentUser();
const language = user.language;
switch (language) {
case 'zh_CN':
return 'zh-CN';
case 'zh_TW':
return 'zh-TW';
case 'en_US':
return 'en';
default:
return 'zh-CN';
}
}
},
mounted() {
//
let el = document.getElementById(this.id);
if (el) {
el.addEventListener('click', () => {
let imagePreview = el.getElementsByClassName('v-note-img-wrapper');
if (imagePreview.length > 0) { //
this.defaultOpen = 'preview';
} else {
this.defaultOpen = null;
}
});
let input = el.getElementsByClassName('auto-textarea-input');
input[0].addEventListener('blur', () => {
this.defaultOpen = 'preview';
});
}
},
methods: {
imgAdd(pos, file){
let param = {
id: getUUID().substring(0, 8)
};
file.prefix = param.id;
this.result.loading = true;
//
param.fileName = file.name.replace("(", "").replace(")", "").replace(" ", "");
this.$fileUpload('/resource/md/upload', file, null, param, () => {
this.$success(this.$t('commons.save_success'));
this.$refs.md.$img2Url(pos, '/resource/md/get/' + param.id + '_' + param.fileName);
this.result.loading = false;
});
this.$emit('imgAdd', file);
},
imgDel(file) {
if (file) {
this.$get('/resource/md/delete/' + file[1].prefix + "_" + file[1].name);
}
},
}
}
</script>
<style scoped>
.mavon-editor {
min-height: 20px;
}
/deep/ .v-note-wrapper {
position: initial;
}
</style>

View File

@ -260,7 +260,7 @@ export default {
// remark: [{max: 1000, message: this.$t('test_track.length_less_than') + '1000', trigger: 'blur'}]
},
customFieldRules: {},
customFieldForm: {},
customFieldForm: null,
formLabelWidth: "100px",
operationType: '',
isCreateContinue: false,
@ -433,7 +433,7 @@ export default {
}
if (this.type === 'add') {
//
parseCustomField(this.form, this.testCaseTemplate, this.customFieldForm, this.customFieldRules);
this.customFieldForm = parseCustomField(this.form, this.testCaseTemplate, this.customFieldRules);
this.form.name = this.testCaseTemplate.caseName;
this.form.stepDescription = this.testCaseTemplate.stepDescription;
this.form.expectedResult = this.testCaseTemplate.expectedResult;
@ -539,7 +539,7 @@ export default {
this.setTestCaseExtInfo(testCase);
this.getSelectOptions();
//
parseCustomField(this.form, this.testCaseTemplate, this.customFieldForm, this.customFieldRules, buildTestCaseOldFields(this.form));
this.customFieldForm = parseCustomField(this.form, this.testCaseTemplate, this.customFieldRules, buildTestCaseOldFields(this.form));
this.reload();
} else {
this.initTestCases(testCase);
@ -559,7 +559,7 @@ export default {
this.form.maintainer = user.id;
this.form.tags = [];
this.getSelectOptions();
parseCustomField(this.form, this.testCaseTemplate, this.customFieldForm, this.customFieldRules);
this.customFieldForm = parseCustomField(this.form, this.testCaseTemplate, this.customFieldRules);
this.reload();
}
},
@ -634,7 +634,7 @@ export default {
}
this.form.module = testCase.nodeId;
//
parseCustomField(this.form, this.testCaseTemplate, this.customFieldForm, this.customFieldRules, testCase ? buildTestCaseOldFields(this.form) : null);
this.customFieldForm = parseCustomField(this.form, this.testCaseTemplate, this.customFieldRules, testCase ? buildTestCaseOldFields(this.form) : null);
this.setDefaultValue();
//
this.reloadForm();

View File

@ -100,6 +100,7 @@ import IssueRelateList from "@/business/components/track/case/components/IssueRe
import {deleteIssueRelate, getIssuesByCaseId} from "@/network/Issue";
import {getIssueTemplate} from "@/network/custom-field-template";
import {getCustomFieldValue, getTableHeaderWithCustomFields} from "@/common/js/tableUtils";
import {LOCAL} from "@/common/js/constants";
export default {
name: "TestCaseIssueRelate",
components: {IssueRelateList, IssueDescriptionTableItem, MsTableColumn, MsTable, TestPlanIssueEdit},
@ -134,7 +135,7 @@ export default {
getIssueTemplate()
.then((template) => {
this.issueTemplate = template;
if (this.issueTemplate.platform === 'metersphere') {
if (this.issueTemplate.platform === LOCAL) {
this.isThirdPart = false;
} else {
this.isThirdPart = true;

View File

@ -3,7 +3,7 @@
<el-scrollbar>
<el-form :model="form" :rules="rules" label-position="right" label-width="80px" ref="form">
<el-form-item :label="$t('commons.title')" prop="title">
<el-form-item v-if="!enableThirdPartTemplate" :label="$t('commons.title')" prop="title">
<el-input v-model="form.title" autocomplete="off" class="top-input-class"></el-input>
<el-tooltip :content="$t('commons.follow')" placement="bottom" effect="dark" v-if="!showFollow">
<i class="el-icon-star-off" style="color: #783987; font-size: 25px; margin-left: 15px;cursor: pointer;position: relative;top: 5px" @click="saveFollow" />
@ -14,21 +14,31 @@
</el-form-item>
<!-- 自定义字段 -->
<el-form v-if="isFormAlive" :model="customFieldForm" :rules="customFieldRules" ref="customFieldForm"
<el-form :model="customFieldForm" :rules="customFieldRules" ref="customFieldForm"
class="case-form">
<el-row class="custom-field-row">
<el-col :span="8" v-for="(item, index) in issueTemplate.customFields" :key="index">
<el-form-item :label="item.system ? $t(systemNameMap[item.name]) : item.name" :prop="item.name"
:label-width="formLabelWidth">
<custom-filed-component @reload="reloadForm" :data="item" :form="customFieldForm" prop="defaultValue"/>
</el-form-item>
</el-col>
<span class="custom-item" v-for="(item, index) in issueTemplate.customFields" :key="index">
<el-col :span="8" v-if="item.type !== 'richText'">
<el-form-item :label="item.system ? $t(systemNameMap[item.name]) : item.name" :prop="item.name"
:label-width="formLabelWidth">
<custom-filed-component :data="item" :form="customFieldForm" prop="defaultValue"/>
</el-form-item>
</el-col>
<div v-else>
<el-col :span="24">
<el-form-item :label="item.system ? $t(systemNameMap[item.name]) : item.name" :prop="item.name"
:label-width="formLabelWidth">
<custom-filed-component :data="item" :form="customFieldForm" prop="defaultValue"/>
</el-form-item>
</el-col>
</div>
</span>
</el-row>
</el-form>
<form-rich-text-item :title="$t('custom_field.issue_content')" :data="form" prop="description"/>
<form-rich-text-item v-if="!enableThirdPartTemplate" :title="$t('custom_field.issue_content')" :data="form" prop="description"/>
<el-row class="custom-field-row">
<el-row v-if="!enableThirdPartTemplate" class="custom-field-row">
<el-col :span="8" v-if="hasTapdId">
<el-form-item :label-width="formLabelWidth" :label="$t('test_track.issue.tapd_current_owner')"
prop="tapdUsers">
@ -85,8 +95,17 @@ import {buildCustomFields, parseCustomField} from "@/common/js/custom_field";
import CustomFiledComponent from "@/business/components/settings/workspace/template/CustomFiledComponent";
import TestCaseIssueList from "@/business/components/track/issue/TestCaseIssueList";
import IssueEditDetail from "@/business/components/track/issue/IssueEditDetail";
import {getCurrentProjectID, getCurrentUser, getCurrentUserId, getCurrentWorkspaceId} from "@/common/js/utils";
import {
getCurrentProjectID,
getCurrentUser,
getCurrentUserId,
getCurrentWorkspaceId,
hasLicense
} from "@/common/js/utils";
import {getIssueTemplate} from "@/network/custom-field-template";
import {getIssueThirdPartTemplate} from "@/network/Issue";
import {getCurrentProject} from "@/network/project";
import {JIRA} from "@/common/js/constants";
export default {
name: "IssueEditDetail",
@ -105,11 +124,10 @@ export default {
issueId:'',
result: {},
relateFields: [],
isFormAlive: true,
showFollow:false,
formLabelWidth: "150px",
issueTemplate: {},
customFieldForm: {},
customFieldForm: null,
customFieldRules: {},
rules: {
title: [
@ -131,7 +149,8 @@ export default {
zentaoUsers: [],
Builds: [],
hasTapdId: false,
hasZentaoId: false
hasZentaoId: false,
currentProject: null
};
},
props: {
@ -153,17 +172,35 @@ export default {
},
projectId() {
return getCurrentProjectID();
}
},
enableThirdPartTemplate() {
return hasLicense() && this.currentProject && this.currentProject.thirdPartTemplate && this.currentProject.platform === JIRA;
},
},
methods: {
open(data) {
let initAddFuc = this.initEdit;
getIssueTemplate()
.then((template) => {
this.issueTemplate = template;
this.getThirdPartyInfo();
initAddFuc(data);
getCurrentProject((responseData) => {
this.currentProject = responseData;
this.$nextTick(() => {
if (this.enableThirdPartTemplate) {
getIssueThirdPartTemplate()
.then((template) => {
this.issueTemplate = template;
this.getThirdPartyInfo();
initAddFuc(data);
});
} else {
getIssueTemplate()
.then((template) => {
this.issueTemplate = template;
this.getThirdPartyInfo();
initAddFuc(data);
});
}
});
});
if(data&&data.id){
this.$get('/issues/follow/' + data.id, response => {
this.form.follows = response.data;
@ -218,7 +255,7 @@ export default {
this.form.options = data.options ? JSON.parse(data.options) : [];
}
if (data.id) {
this.issueId = data.id
this.issueId = data.id
this.url = 'issues/update';
} else {
//copy
@ -238,17 +275,13 @@ export default {
this.form.creator = getCurrentUserId();
}
}
parseCustomField(this.form, this.issueTemplate, this.customFieldForm, this.customFieldRules);
this.customFieldForm = parseCustomField(this.form, this.issueTemplate, this.customFieldRules);
this.$nextTick(() => {
if (this.$refs.testCaseIssueList) {
this.$refs.testCaseIssueList.initTableData();
}
});
},
reloadForm() {
this.isFormAlive = false;
this.$nextTick(() => (this.isFormAlive = true));
},
save() {
let isValidate = true;
this.$refs['form'].validate((valid) => {
@ -281,6 +314,8 @@ export default {
if (this.planId) {
param.resourceId = this.planId;
}
param.thirdPartPlatform = this.enableThirdPartTemplate;
return param;
},
_save() {

View File

@ -168,7 +168,7 @@ import {
import MsTableHeader from "@/business/components/common/components/MsTableHeader";
import IssueDescriptionTableItem from "@/business/components/track/issue/IssueDescriptionTableItem";
import IssueEdit from "@/business/components/track/issue/IssueEdit";
import {getIssues, syncIssues} from "@/network/Issue";
import {getIssues, getIssueThirdPartTemplate, syncIssues} from "@/network/Issue";
import {
getCustomFieldValue,
getCustomTableWidth,
@ -176,9 +176,11 @@ import {
} from "@/common/js/tableUtils";
import MsContainer from "@/business/components/common/components/MsContainer";
import MsMainContainer from "@/business/components/common/components/MsMainContainer";
import {getCurrentProjectID, getCurrentWorkspaceId} from "@/common/js/utils";
import {getCurrentProjectID, getCurrentWorkspaceId, hasLicense} from "@/common/js/utils";
import {getIssueTemplate} from "@/network/custom-field-template";
import {getProjectMember} from "@/network/user";
import {JIRA, LOCAL} from "@/common/js/constants";
import {getCurrentProject} from "@/network/project";
export default {
name: "IssueList",
@ -215,7 +217,8 @@ export default {
issueTemplate: {},
members: [],
isThirdPart: false,
creatorFilters: []
creatorFilters: [],
currentProject: null
};
},
watch: {
@ -229,25 +232,21 @@ export default {
getProjectMember((data) => {
this.members = data;
});
getIssueTemplate()
.then((template) => {
this.issueTemplate = template;
if (this.issueTemplate.platform === 'metersphere') {
this.isThirdPart = false;
} else {
this.isThirdPart = true;
}
this.fields = getTableHeaderWithCustomFields('ISSUE_LIST', this.issueTemplate.customFields);
if (!this.isThirdPart) {
for (let i = 0; i < this.fields.length; i++) {
if (this.fields[i].id === 'platformStatus') {
this.fields.splice(i, 1);
break;
}
}
}
if (this.$refs.table) this.$refs.table.reloadTable();
});
getCurrentProject((responseData) => {
this.currentProject = responseData;
if (hasLicense() && this.currentProject.thirdPartTemplate && this.currentProject.platform === JIRA) {
getIssueThirdPartTemplate()
.then((template) => {
this.initFields(template);
});
} else {
getIssueTemplate()
.then((template) => {
this.initFields(template);
});
}
});
this.getIssues();
},
computed: {
@ -277,6 +276,24 @@ export default {
getCustomFieldValue(row, field) {
return getCustomFieldValue(row, field, this.members);
},
initFields(template) {
this.issueTemplate = template;
if (this.issueTemplate.platform === LOCAL) {
this.isThirdPart = false;
} else {
this.isThirdPart = true;
}
this.fields = getTableHeaderWithCustomFields('ISSUE_LIST', this.issueTemplate.customFields);
if (!this.isThirdPart) {
for (let i = 0; i < this.fields.length; i++) {
if (this.fields[i].id === 'platformStatus') {
this.fields.splice(i, 1);
break;
}
}
}
if (this.$refs.table) this.$refs.table.reloadTable();
},
getIssues() {
this.page.condition.projectId = this.projectId;
this.page.condition.workspaceId= this.workspaceId;
@ -311,9 +328,6 @@ export default {
});
},
btnDisable(row) {
if (this.issueTemplate.platform == "metersphere" && row.platform == 'Local') {
return false;
}
if (this.issueTemplate.platform !== row.platform) {
return true;
}

View File

@ -391,7 +391,7 @@ export default {
}
}
this.testCase = item;
parseCustomField(this.testCase, this.testCaseTemplate, null, null, buildTestCaseOldFields(this.testCase));
parseCustomField(this.testCase, this.testCaseTemplate, null, buildTestCaseOldFields(this.testCase));
this.isCustomFiledActive = true;
if (!this.testCase.actualResult) {
// ,使

View File

@ -327,7 +327,7 @@ export default {
if (!item.stepModel) {
item.stepModel = 'STEP';
}
parseCustomField(item, this.testCaseTemplate, null, null, buildTestCaseOldFields(item));
parseCustomField(item, this.testCaseTemplate, null, buildTestCaseOldFields(item));
this.isCustomFiledActive = true;
this.testCase = item;
if (!this.testCase.actualResult) {

@ -1 +1 @@
Subproject commit abb1eae9aff953ca0be49bfb413454e435d627ee
Subproject commit 275aff0cdaf7556e02c0fc479cb19d2f962695c2

View File

@ -38,6 +38,7 @@ export const EN_US = 'en_US';
export const TAPD = 'Tapd';
export const JIRA = 'Jira';
export const ZEN_TAO = 'Zentao';
export const LOCAL = 'Local';
export const AZURE_DEVOPS = 'AzureDevops';
export const GROUP_SYSTEM = 'SYSTEM';

View File

@ -14,7 +14,7 @@ function setDefaultValue(item, value) {
* @param rules 自定义表单的校验规则
* @param oldFields 用于兼容旧版本数据
*/
export function parseCustomField(data, template, customFieldForm, rules, oldFields) {
export function parseCustomField(data, template, rules, oldFields) {
let hasOldData = false;
if (!data.customFields) {
// 旧数据
@ -25,6 +25,8 @@ export function parseCustomField(data, template, customFieldForm, rules, oldFiel
data.customFields = JSON.parse(data.customFields);
}
let customFieldForm = {};
// 设置页面显示的默认值
template.customFields.forEach(item => {
@ -82,10 +84,10 @@ export function parseCustomField(data, template, customFieldForm, rules, oldFiel
}
}
if (customFieldForm) {
customFieldForm[item.name] = item.defaultValue;
}
customFieldForm[item.name] = item.defaultValue;
});
return customFieldForm;
}
// 将template的属性值设置给customFields

View File

@ -1,5 +1,6 @@
// 模板
import i18n from "@/i18n/i18n";
import {AZURE_DEVOPS, JIRA, LOCAL, TAPD, ZEN_TAO} from "@/common/js/constants";
export const CUSTOM_FIELD_TYPE_OPTION = [
{value: 'input',text: i18n.t('workspace.custom_filed.input')},
@ -10,7 +11,9 @@ export const CUSTOM_FIELD_TYPE_OPTION = [
{value: 'checkbox',text: i18n.t('workspace.custom_filed.checkbox')},
{value: 'member',text: i18n.t('workspace.custom_filed.member')},
{value: 'multipleMember',text: i18n.t('workspace.custom_filed.multipleMember')},
{value: 'data',text: i18n.t('workspace.custom_filed.data')},
{value: 'date',text: i18n.t('workspace.custom_filed.date')},
{value: 'datetime',text: i18n.t('workspace.custom_filed.datetime')},
{value: 'richText',text: i18n.t('workspace.custom_filed.richText')},
{value: 'int',text: i18n.t('workspace.custom_filed.int')},
{value: 'float',text: i18n.t('workspace.custom_filed.float')},
{value: 'multipleInput',text: i18n.t('workspace.custom_filed.multipleInput')}
@ -26,11 +29,11 @@ export const CASE_TYPE_OPTION = [
];
export const ISSUE_PLATFORM_OPTION = [
{value: 'Local',text: 'Metersphere'},
{value: 'Jira',text: 'JIRA'},
{value: 'Tapd',text: 'Tapd'},
{value: 'Zentao',text: '禅道'},
{value: 'AzureDevops',text: 'Azure Devops'},
{value: LOCAL, text: 'Metersphere'},
{value: TAPD, text: 'Tapd'},
{value: JIRA, text: 'JIRA'},
{value: ZEN_TAO, text: '禅道'},
{value: AZURE_DEVOPS, text: 'Azure Devops'},
];
export const FIELD_TYPE_MAP = {
@ -42,7 +45,9 @@ export const FIELD_TYPE_MAP = {
checkbox: 'workspace.custom_filed.checkbox',
member: 'workspace.custom_filed.member',
multipleMember: 'workspace.custom_filed.multipleMember',
data: 'workspace.custom_filed.data',
date: 'workspace.custom_filed.date',
datetime: 'workspace.custom_filed.datetime',
richText: 'workspace.custom_filed.richText',
int: 'workspace.custom_filed.int',
float: 'workspace.custom_filed.float',
multipleInput: 'workspace.custom_filed.multipleInput'

View File

@ -459,10 +459,12 @@ export function getCustomFieldValue(row, field, members) {
return values;
}
} else if (['radio', 'select'].indexOf(field.type) > -1) {
for (let j = 0; j < field.options.length; j++) {
let option = field.options[j];
if (option.value === item.value) {
return field.system ? i18n.t(option.text) : option.text;
if (field.options) {
for (let j = 0; j < field.options.length; j++) {
let option = field.options[j];
if (option.value === item.value) {
return field.system ? i18n.t(option.text) : option.text;
}
}
}
}

View File

@ -494,7 +494,9 @@ export default {
checkbox: 'Checkbox',
member: 'Member',
multipleMember: 'MultipleMember',
data: 'Data',
date: 'DatePicker',
datetime: 'DateTimePicker',
richText: 'RichText',
int: 'Int',
float: 'Float',
multipleInput: 'MultipleInput'

View File

@ -496,7 +496,9 @@ export default {
checkbox: '多选框',
member: '单选成员',
multipleMember: '多选成员',
data: '日期',
date: '日期选择器',
datetime: '日期时间选择器',
richText: '富文本框',
int: '整型',
float: '浮点型',
multipleInput: '多值输入框'

View File

@ -495,7 +495,9 @@ export default {
checkbox: '多選框',
member: '單選成員',
multipleMember: '多選成員',
data: '日期',
date: '日期選擇器',
datetime: '日期時間選擇器',
richText: '富文本框',
int: '整型',
float: '浮點型',
multipleInput: '多值輸入框'

View File

@ -92,3 +92,19 @@ export function syncIssues(success) {
export function deleteIssueRelate(param, callback) {
return basePost('/issues/delete/relate', param, callback);
}
export function getIssueThirdPartTemplate() {
return new Promise(resolve => {
baseGet('/xpack/issue/template/' + getCurrentProjectID(), (data) => {
let template = data;
if (template.customFields) {
template.customFields.forEach(item => {
if (item.options) {
item.options = JSON.parse(item.options);
}
});
}
resolve(template);
})
});
}

View File

@ -0,0 +1,10 @@
import {baseGet} from "@/network/base-network";
import {getCurrentProjectID} from "@/common/js/utils";
export function getProject(projectId, callback) {
return projectId ? baseGet('/project/get/' + projectId, callback) : {};
}
export function getCurrentProject(callback) {
return getProject(getCurrentProjectID(), callback);
}