fix: 解决冲突

This commit is contained in:
chenjianxing 2020-12-16 17:04:45 +08:00
commit 992b367f53
35 changed files with 463 additions and 1867 deletions

View File

@ -7,6 +7,7 @@ import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.automation.ApiScenarioRequest; import io.metersphere.api.dto.automation.ApiScenarioRequest;
import io.metersphere.api.dto.automation.ReferenceDTO; import io.metersphere.api.dto.automation.ReferenceDTO;
import io.metersphere.api.dto.definition.*; import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
import io.metersphere.api.service.ApiDefinitionService; import io.metersphere.api.service.ApiDefinitionService;
import io.metersphere.base.domain.ApiDefinition; import io.metersphere.base.domain.ApiDefinition;
import io.metersphere.commons.constants.RoleConstants; import io.metersphere.commons.constants.RoleConstants;
@ -93,7 +94,7 @@ public class ApiDefinitionController {
@PostMapping(value = "/import", consumes = {"multipart/form-data"}) @PostMapping(value = "/import", consumes = {"multipart/form-data"})
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public String testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) { public ApiDefinitionImport testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) {
return apiDefinitionService.apiTestImport(file, request); return apiDefinitionService.apiTestImport(file, request);
} }

View File

@ -13,5 +13,7 @@ public class ApiTestImportRequest {
private String projectId; private String projectId;
private String platform; private String platform;
private Boolean useEnvironment; private Boolean useEnvironment;
// 来自场景的导入不需要存储
private boolean saved = true;
private String swaggerUrl; private String swaggerUrl;
} }

View File

@ -32,7 +32,7 @@ public class MsParser extends ApiImportAbstractParser {
if (testObject.get("projectName") != null) { if (testObject.get("projectName") != null) {
return parseMsFormat(testStr, request); return parseMsFormat(testStr, request);
} else { } else {
return parsePluginFormat(testObject); return parsePluginFormat(testObject, request.isSaved());
} }
} }
@ -53,14 +53,16 @@ public class MsParser extends ApiImportAbstractParser {
return apiDefinitionImport; return apiDefinitionImport;
} }
private ApiDefinitionImport parsePluginFormat(JSONObject testObject) { private ApiDefinitionImport parsePluginFormat(JSONObject testObject, boolean isSaved) {
List<ApiDefinitionResult> results = new ArrayList<>(); List<ApiDefinitionResult> results = new ArrayList<>();
ApiDefinitionImport apiImport = new ApiDefinitionImport(); ApiDefinitionImport apiImport = new ApiDefinitionImport();
apiImport.setProtocol(RequestType.HTTP); apiImport.setProtocol(RequestType.HTTP);
apiImport.setData(results); apiImport.setData(results);
testObject.keySet().forEach(tag -> { testObject.keySet().forEach(tag -> {
ApiModule module = apiModuleService.getNewModule(tag, this.projectId, 1); ApiModule module = apiModuleService.getNewModule(tag, this.projectId, 1);
if (isSaved) {
createModule(module); createModule(module);
}
JSONObject requests = testObject.getJSONObject(tag); JSONObject requests = testObject.getJSONObject(tag);
requests.keySet().forEach(requestName -> { requests.keySet().forEach(requestName -> {

View File

@ -30,17 +30,17 @@ public class PostmanParser extends ApiImportAbstractParser {
List<PostmanKeyValue> variables = postmanCollection.getVariable(); List<PostmanKeyValue> variables = postmanCollection.getVariable();
ApiDefinitionImport apiImport = new ApiDefinitionImport(); ApiDefinitionImport apiImport = new ApiDefinitionImport();
List<ApiDefinitionResult> results = new ArrayList<>(); List<ApiDefinitionResult> results = new ArrayList<>();
parseItem(postmanCollection.getItem(), variables, results, buildModule(postmanCollection.getInfo().getName(), null)); parseItem(postmanCollection.getItem(), variables, results, buildModule(postmanCollection.getInfo().getName(), null, request.isSaved()), request.isSaved());
apiImport.setData(results); apiImport.setData(results);
return apiImport; return apiImport;
} }
private void parseItem(List<PostmanItem> items, List<PostmanKeyValue> variables, List<ApiDefinitionResult> results, ApiModule parentModule) { private void parseItem(List<PostmanItem> items, List<PostmanKeyValue> variables, List<ApiDefinitionResult> results, ApiModule parentModule, boolean isSaved) {
for (PostmanItem item : items) { for (PostmanItem item : items) {
List<PostmanItem> childItems = item.getItem(); List<PostmanItem> childItems = item.getItem();
if (childItems != null) { if (childItems != null) {
ApiModule module = buildModule(item.getName(), parentModule); ApiModule module = buildModule(item.getName(), parentModule, isSaved);
parseItem(childItems, variables, results, module); parseItem(childItems, variables, results, module, isSaved);
} else { } else {
ApiDefinitionResult request = parsePostman(item); ApiDefinitionResult request = parsePostman(item);
if (request != null) { if (request != null) {
@ -53,7 +53,7 @@ public class PostmanParser extends ApiImportAbstractParser {
} }
} }
private ApiModule buildModule(String name, ApiModule parentModule) { private ApiModule buildModule(String name, ApiModule parentModule, boolean isSaved) {
apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class); apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class);
ApiModule module; ApiModule module;
if (parentModule != null) { if (parentModule != null) {
@ -62,7 +62,9 @@ public class PostmanParser extends ApiImportAbstractParser {
} else { } else {
module = apiModuleService.getNewModule(name, this.projectId, 1); module = apiModuleService.getNewModule(name, this.projectId, 1);
} }
if (isSaved) {
createModule(module); createModule(module);
}
return module; return module;
} }

View File

@ -39,11 +39,11 @@ public class Swagger2Parser extends ApiImportAbstractParser {
} }
ApiDefinitionImport definitionImport = new ApiDefinitionImport(); ApiDefinitionImport definitionImport = new ApiDefinitionImport();
this.projectId = request.getProjectId(); this.projectId = request.getProjectId();
definitionImport.setData(parseRequests(swagger)); definitionImport.setData(parseRequests(swagger, request.isSaved()));
return definitionImport; return definitionImport;
} }
private List<ApiDefinitionResult> parseRequests(Swagger swagger) { private List<ApiDefinitionResult> parseRequests(Swagger swagger, boolean isSaved) {
Map<String, Path> paths = swagger.getPaths(); Map<String, Path> paths = swagger.getPaths();
Set<String> pathNames = paths.keySet(); Set<String> pathNames = paths.keySet();
@ -62,7 +62,7 @@ public class Swagger2Parser extends ApiImportAbstractParser {
parseParameters(operation, request); parseParameters(operation, request);
apiDefinition.setRequest(JSON.toJSONString(request)); apiDefinition.setRequest(JSON.toJSONString(request));
apiDefinition.setResponse(JSON.toJSONString(parseResponse(operation.getResponses()))); apiDefinition.setResponse(JSON.toJSONString(parseResponse(operation.getResponses())));
buildModule(apiDefinition, operation); buildModule(apiDefinition, operation, isSaved);
results.add(apiDefinition); results.add(apiDefinition);
} }
} }
@ -71,13 +71,15 @@ public class Swagger2Parser extends ApiImportAbstractParser {
return results; return results;
} }
private void buildModule(ApiDefinitionResult apiDefinition, Operation operation) { private void buildModule(ApiDefinitionResult apiDefinition, Operation operation, boolean isSaved) {
List<String> tags = operation.getTags(); List<String> tags = operation.getTags();
if (tags != null) { if (tags != null) {
tags.forEach(tag -> { tags.forEach(tag -> {
apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class); apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class);
ApiModule module = apiModuleService.getNewModule(tag, this.projectId, 1); ApiModule module = apiModuleService.getNewModule(tag, this.projectId, 1);
if (isSaved) {
createModule(module); createModule(module);
}
apiDefinition.setModuleId(module.getId()); apiDefinition.setModuleId(module.getId());
}); });
} }
@ -304,7 +306,7 @@ public class Swagger2Parser extends ApiImportAbstractParser {
private void parseFormDataParameters(FormParameter parameter, Body body) { private void parseFormDataParameters(FormParameter parameter, Body body) {
List<KeyValue> keyValues = Optional.ofNullable(body.getKvs()).orElse(new ArrayList<>()); List<KeyValue> keyValues = Optional.ofNullable(body.getKvs()).orElse(new ArrayList<>());
KeyValue kv = new KeyValue(parameter.getName(), "", getDefaultStringValue(parameter.getDescription())); KeyValue kv = new KeyValue(parameter.getName(), "", getDefaultStringValue(parameter.getDescription()));
if (StringUtils.equals(parameter.getType(), "file") ) { if (StringUtils.equals(parameter.getType(), "file")) {
kv.setType("file"); kv.setType("file");
} }
keyValues.add(kv); keyValues.add(kv);

View File

@ -4,7 +4,6 @@ import com.alibaba.fastjson.JSON;
import io.metersphere.api.jmeter.TestResult; import io.metersphere.api.jmeter.TestResult;
import io.metersphere.base.domain.ApiDefinitionExecResult; import io.metersphere.base.domain.ApiDefinitionExecResult;
import io.metersphere.base.mapper.ApiDefinitionExecResultMapper; import io.metersphere.base.mapper.ApiDefinitionExecResultMapper;
import io.metersphere.base.mapper.ext.ExtApiDefinitionExecResultMapper;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -18,14 +17,9 @@ import java.util.UUID;
public class ApiDefinitionExecResultService { public class ApiDefinitionExecResultService {
@Resource @Resource
private ApiDefinitionExecResultMapper apiDefinitionExecResultMapper; private ApiDefinitionExecResultMapper apiDefinitionExecResultMapper;
@Resource
private ExtApiDefinitionExecResultMapper extApiDefinitionExecResultMapper;
public void saveApiResult(TestResult result) { public void saveApiResult(TestResult result) {
result.getScenarios().get(0).getRequestResults().forEach(item -> { result.getScenarios().get(0).getRequestResults().forEach(item -> {
// 清理原始资源每个执行 保留一条结果
extApiDefinitionExecResultMapper.deleteByResourceId(item.getName());
ApiDefinitionExecResult saveResult = new ApiDefinitionExecResult(); ApiDefinitionExecResult saveResult = new ApiDefinitionExecResult();
saveResult.setId(UUID.randomUUID().toString()); saveResult.setId(UUID.randomUUID().toString());
saveResult.setUserId(Objects.requireNonNull(SessionUtils.getUser()).getId()); saveResult.setUserId(Objects.requireNonNull(SessionUtils.getUser()).getId());

View File

@ -326,7 +326,7 @@ public class ApiDefinitionService {
* @return * @return
*/ */
public APIReportResult getDbResult(String testId) { public APIReportResult getDbResult(String testId) {
ApiDefinitionExecResult result = extApiDefinitionExecResultMapper.selectByResourceId(testId); ApiDefinitionExecResult result = extApiDefinitionExecResultMapper.selectMaxResultByResourceId(testId);
if (result == null) { if (result == null) {
return null; return null;
} }
@ -336,7 +336,7 @@ public class ApiDefinitionService {
} }
public String apiTestImport(MultipartFile file, ApiTestImportRequest request) { public ApiDefinitionImport apiTestImport(MultipartFile file, ApiTestImportRequest request) {
ApiImportParser apiImportParser = ApiImportParserFactory.getApiImportParser(request.getPlatform()); ApiImportParser apiImportParser = ApiImportParserFactory.getApiImportParser(request.getPlatform());
ApiDefinitionImport apiImport = null; ApiDefinitionImport apiImport = null;
try { try {
@ -345,8 +345,10 @@ public class ApiDefinitionService {
LogUtil.error(e.getMessage(), e); LogUtil.error(e.getMessage(), e);
MSException.throwException(Translator.get("parse_data_error")); MSException.throwException(Translator.get("parse_data_error"));
} }
if (request.isSaved()) {
importApiTest(request, apiImport); importApiTest(request, apiImport);
return "SUCCESS"; }
return apiImport;
} }
private void importApiTest(ApiTestImportRequest importRequest, ApiDefinitionImport apiImport) { private void importApiTest(ApiTestImportRequest importRequest, ApiDefinitionImport apiImport) {

View File

@ -6,7 +6,6 @@ public interface ExtApiDefinitionExecResultMapper {
void deleteByResourceId(String id); void deleteByResourceId(String id);
ApiDefinitionExecResult selectByResourceId(String resourceId); ApiDefinitionExecResult selectMaxResultByResourceId(String resourceId);
} }

View File

@ -5,8 +5,8 @@
delete from api_definition_exec_result where resource_id = #{id,jdbcType=VARCHAR} delete from api_definition_exec_result where resource_id = #{id,jdbcType=VARCHAR}
</delete> </delete>
<select id="selectByResourceId" parameterType="java.lang.String" resultType="io.metersphere.base.domain.ApiDefinitionExecResult"> <select id="selectMaxResultByResourceId" parameterType="java.lang.String" resultType="io.metersphere.base.domain.ApiDefinitionExecResult">
select * from api_definition_exec_result select * from api_definition_exec_result
where resource_id = #{resourceId,jdbcType=VARCHAR} where resource_id = #{resourceId,jdbcType=VARCHAR} ORDER BY update_time DESC LIMIT 1
</select> </select>
</mapper> </mapper>

View File

@ -94,12 +94,24 @@
when 'error' then '未通过' when 'error' then '未通过'
ELSE '未执行' end as status , ELSE '未执行' end as status ,
CONCAT(FORMAT(SUM(IF(t2.`status` = 'success', 1, 0))/ COUNT(t1.id)*100, 2), '%') passRate CONCAT(FORMAT(SUM(IF(t2.`status` = 'success', 1, 0))/ COUNT(t1.id)*100, 2), '%') passRate
from api_test_case t1 left join api_definition_exec_result t2 on t1.id = t2.resource_id from api_test_case t1 left join (
select
a.status, a.id, a.resource_id
from
api_definition_exec_result a
left join (
select
max(start_time) start_time , id, resource_id
from
api_definition_exec_result
group by
resource_id ) as b on a.id = b.id
where
a.start_time = b.start_time)as t2 on t1.id = t2.resource_id
group by t1.api_definition_id having t1.api_definition_id in group by t1.api_definition_id having t1.api_definition_id in
<foreach collection="ids" item="v" separator="," open="(" close=")"> <foreach collection="ids" item="v" separator="," open="(" close=")">
#{v} #{v}
</foreach> </foreach>
order by t2.end_time desc;
</select> </select>
<sql id="combine"> <sql id="combine">

View File

@ -146,15 +146,45 @@
</sql> </sql>
<select id="list" resultType="io.metersphere.api.dto.definition.ApiTestCaseResult"> <select id="list" resultType="io.metersphere.api.dto.definition.ApiTestCaseResult">
select atc.id, atc.project_id, select
atc.name,atc.priority,atc.api_definition_id,T1.name as createUser ,T2.name as updateUser, atc.id,
atc.description,atc.request,atc.response,atc.create_user_id, atc.project_id,
atc.create_time,atc.update_user_id, atc.update_time,ader.status execResult atc.name,
from api_test_case atc left join user T1 on atc.create_user_id = T1.id left join user T2 on atc.priority,
atc.update_user_id = T2.id left join api_definition_exec_result ader on atc.id = ader.resource_id atc.api_definition_id,
u1.name as createUser ,
u2.name as updateUser,
atc.description,
atc.request,
atc.response,
atc.create_user_id,
atc.create_time,
atc.update_user_id,
atc.update_time,
ader.status execResult
from
api_test_case atc
left join user u1 on
atc.create_user_id = u1.id
left join user u2 on
atc.update_user_id = u2.id
left join (
select
a.status, a.id, a.resource_id
from
api_definition_exec_result a
left join (
select
max(start_time) start_time , id, resource_id
from
api_definition_exec_result
group by
resource_id ) as b on a.id = b.id
where
a.start_time = b.start_time) as ader
on atc.id = ader.resource_id
<where> <where>
<if test="request.name != null and request.name!=''"> <if test="request.name != null and request.name!=''">
and atc.name like CONCAT('%', #{request.name},'%') and atc.name like CONCAT('%', #{request.name},'%')
</if> </if>
<if test="request.id != null and request.id!=''"> <if test="request.id != null and request.id!=''">

View File

@ -84,9 +84,4 @@ public class ProjectController {
projectService.updateProject(Project); projectService.updateProject(Project);
} }
@PostMapping("/search")
public List<ProjectDTO> searchProject(@RequestBody ProjectRequest projectRequest) {
projectRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
return projectService.getProjectList(projectRequest);
}
} }

View File

@ -1,45 +0,0 @@
FROM alpine:latest
LABEL maintainer="support@fit2cloud.com"
ENV JMETER_VERSION "5.3"
ENV KAFKA_BACKEND_LISTENER_VERSION "1.0.4"
#定义时区参数
ENV TZ=Asia/Shanghai
RUN apk update && \
apk upgrade && \
apk add --update openjdk8 wget tar bash && \
wget https://mirrors.tuna.tsinghua.edu.cn/apache/jmeter/binaries/apache-jmeter-${JMETER_VERSION}.tgz && \
wget https://jmeter-plugins.org/files/packages/jpgc-casutg-2.9.zip && \
wget https://jmeter-plugins.org/files/packages/jpgc-tst-2.5.zip && \
wget https://github.com/metersphere/jmeter-backend-listener-kafka/releases/download/v${KAFKA_BACKEND_LISTENER_VERSION}/jmeter.backendlistener.kafka-${KAFKA_BACKEND_LISTENER_VERSION}.jar && \
wget https://github.com/metersphere/jmeter-plugins-for-apache-dubbo/releases/download/2.7.7/jmeter-plugins-dubbo-2.7.7-jar-with-dependencies.jar && \
wget -q "http://search.maven.org/remotecontent?filepath=mysql/mysql-connector-java/5.1.49/mysql-connector-java-5.1.49.jar" -O mysql-connector-java.jar && \
wget -q "http://search.maven.org/remotecontent?filepath=com/oracle/database/jdbc/ojdbc8/19.7.0.0/ojdbc8-19.7.0.0.jar" -O ojdbc8.jar && \
wget -q "http://search.maven.org/remotecontent?filepath=org/postgresql/postgresql/42.2.14/postgresql-42.2.14.jar" -O postgresql.jar && \
wget -q "http://search.maven.org/remotecontent?filepath=com/microsoft/sqlserver/mssql-jdbc/7.4.1.jre8/mssql-jdbc-7.4.1.jre8.jar" -O mssql-jdbc.jar && \
mkdir -p /opt/jmeter && \
tar -zxf apache-jmeter-${JMETER_VERSION}.tgz -C /opt/jmeter/ --strip-components=1 && \
unzip -o jpgc-casutg-2.9.zip -d /tmp/ && mv /tmp/lib/ext/jmeter-plugins-casutg-2.9.jar /opt/jmeter/lib/ext && \
unzip -o jpgc-tst-2.5.zip -d /tmp/ && mv /tmp/lib/ext/jmeter-plugins-tst-2.5.jar /opt/jmeter/lib/ext && \
mv jmeter.backendlistener.kafka-${KAFKA_BACKEND_LISTENER_VERSION}.jar /opt/jmeter/lib/ext && \
mv jmeter-plugins-dubbo-2.7.7-jar-with-dependencies.jar /opt/jmeter/lib/ext && \
mv mysql-connector-java.jar /opt/jmeter/lib/ext && \
mv ojdbc8.jar /opt/jmeter/lib/ext && \
mv postgresql.jar /opt/jmeter/lib/ext && \
mv mssql-jdbc.jar /opt/jmeter/lib/ext && \
rm -rf apache-jmeter-${JMETER_VERSION}.tgz && \
rm -rf jpgc-casutg-2.9.zip && \
rm -rf jpgc-tst-2.5.zip && \
rm -rf jmeter.backendlistener.kafka-${KAFKA_BACKEND_LISTENER_VERSION}.jar && \
rm -rf jmeter-plugins-dubbo-2.7.7-jar-with-dependencies.jar && \
rm -rf /var/cache/apk/* && \
wget -O /usr/bin/tpl https://github.com/schneidexe/tpl/releases/download/v0.5.0/tpl-linux-amd64 && \
chmod +x /usr/bin/tpl && \
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo "$TZ" > /etc/timezone
ENV JMETER_HOME /opt/jmeter
ENV PATH $PATH:$JMETER_HOME/bin:/usr/lib/jvm/java-1.8-openjdk/bin
ADD log4j2.xml $JMETER_HOME/bin/log4j2.xml
ADD jmeter.properties $JMETER_HOME/bin/jmeter.properties

File diff suppressed because it is too large Load Diff

View File

@ -1,116 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<Configuration status="WARN" packages="org.apache.jmeter.gui.logging">
<Appenders>
<File name="jmeter-log" fileName="${sys:jmeter.logfile:-jmeter.log}" append="false">
<PatternLayout>
<pattern>%d %p %c{1.}: %m%n</pattern>
</PatternLayout>
</File>
<GuiLogEvent name="gui-log-event">
<PatternLayout>
<pattern>%d %p %c{1.}: %m%n</pattern>
</PatternLayout>
</GuiLogEvent>
<Kafka name="Kafka" topic="${env:LOG_TOPIC}">
<PatternLayout pattern="${env:REPORT_ID} ${env:RESOURCE_ID} %date %message"/>
<Property name="bootstrap.servers">${env:BOOTSTRAP_SERVERS}</Property>
</Kafka>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="jmeter-log" />
<AppenderRef ref="gui-log-event" />
<AppenderRef ref="Kafka" />
</Root>
<Logger name="org.apache.kafka" level="INFO" />
<Logger name="org.apache.jmeter.junit" level="debug" />
<!--
<Logger name="org.apache.jmeter.control" level="debug" />
<Logger name="org.apache.jmeter.testbeans" level="debug" />
<Logger name="org.apache.jmeter.engine" level="debug" />
<Logger name="org.apache.jmeter.threads" level="debug" />
<Logger name="org.apache.jmeter.gui" level="warn" />
<Logger name="org.apache.jmeter.testelement" level="debug" />
<Logger name="org.apache.jmeter.util" level="warn" />
<Logger name="org.apache.jmeter.protocol.http" level="debug" />
-->
<!-- # For CookieManager, AuthManager etc: -->
<!--
<Logger name="org.apache.jmeter.protocol.http.control" level="debug" />
<Logger name="org.apache.jmeter.protocol.ftp" level="warn" />
<Logger name="org.apache.jmeter.protocol.jdbc" level="debug" />
<Logger name="org.apache.jmeter.protocol.java" level="warn" />
<Logger name="org.apache.jmeter.testelements.property" level="debug" />
-->
<Logger name="org.apache.jorphan" level="info" />
<!--
# Apache HttpClient logging examples
-->
<!-- # Enable header wire + context logging - Best for Debugging -->
<!--
<Logger name="org.apache.http" level="debug" />
<Logger name="org.apache.http.wire" level="error" />
-->
<!-- # Enable full wire + context logging -->
<!-- <Logger name="org.apache.http" level="debug" /> -->
<!-- # Enable context logging for connection management -->
<!-- <Logger name="org.apache.http.impl.conn" level="debug" /> -->
<!-- # Enable context logging for connection management / request execution -->
<!--
<Logger name="org.apache.http.impl.conn" level="debug" />
<Logger name="org.apache.http.impl.client" level="debug" />
<Logger name="org.apache.http.client" level="debug" />
-->
<!--
# Reporting logging configuration examples
-->
<!-- # If you want to debug reporting, uncomment this line -->
<!-- <Logger name="org.apache.jmeter.report" level="debug" /> -->
<!--
# More user specific logging configuration examples.
-->
<!-- <Logger name="org.apache.jorphan.reflect" level="debug" /> -->
<!--
# Warning: Enabling the next debug line causes javax.net.ssl.SSLException: Received fatal alert: unexpected_message
for certain sites when used with the default HTTP Sampler
-->
<!--
<Logger name="org.apache.jmeter.util.HttpSSLProtocolSocketFactory" level="debug" />
<Logger name="org.apache.jmeter.util.JsseSSLManager" level="debug" />
-->
<!--
# Enable Proxy request debug
-->
<!-- <Logger name="org.apache.jmeter.protocol.http.proxy.HttpRequestHdr" level="debug" /> -->
</Loggers>
</Configuration>

View File

@ -1,14 +0,0 @@
FROM registry.fit2cloud.com/metersphere/jmeter-base:0.0.1
LABEL maintainer="support@fit2cloud.com"
EXPOSE 60000
ENV SSL_DISABLED true
ENV TESTS_DIR /test
ADD run-test.sh /run-test.sh
RUN chmod +x /run-test.sh \
&& mkdir /test \
&& mkdir /jmeter-log
WORKDIR /jmeter-log/
ENTRYPOINT /run-test.sh

View File

@ -1,3 +0,0 @@
for file in ${TESTS_DIR}/*.jmx; do
jmeter -n -t ${file} -Jserver.rmi.ssl.disable=${SSL_DISABLED}
done

View File

@ -17,15 +17,21 @@
<el-col :span="5"> <el-col :span="5">
<el-tooltip effect="dark" :content="request.responseResult.responseCode" placement="bottom" :open-delay="800"> <el-tooltip effect="dark" :content="request.responseResult.responseCode" placement="bottom" :open-delay="800">
<div class="url" style="color: #5daf34">{{ request.responseResult.responseCode }}</div> <div style="color: #5daf34" v-if="request.success">{{ request.responseResult.responseCode }}</div>
<div style="color: #FE6F71" v-else>{{ request.responseResult.responseCode }}</div>
</el-tooltip> </el-tooltip>
</el-col> </el-col>
<el-col :span="3"> <el-col :span="3">
<span v-if="request.success">
{{request.responseResult.responseTime}} ms {{request.responseResult.responseTime}} ms
</span>
<span style="color: #FE6F71" v-else>
{{request.responseResult.responseTime}} ms
</span>
</el-col> </el-col>
<el-col :span="2"> <el-col :span="2">
<div class="success"> <div>
<el-tag size="mini" type="success" v-if="request.success"> <el-tag size="mini" type="success" v-if="request.success">
{{ $t('api_report.success') }} {{ $t('api_report.success') }}
</el-tag> </el-tag>

View File

@ -8,33 +8,10 @@
</div> </div>
</el-col> </el-col>
<el-col :span="10"> <el-col :span="10">
<div class="name">{{request.name}}</div>
<el-tooltip effect="dark" :content="request.url" placement="bottom" :open-delay="800"> <el-tooltip effect="dark" :content="request.url" placement="bottom" :open-delay="800">
<div class="url">{{request.url}}</div> <div class="url">{{request.url}}</div>
</el-tooltip> </el-tooltip>
</el-col> </el-col>
<el-col :span="4">
{{request.startTime | timestampFormatDate(true) }}
</el-col>
<el-col :span="2">
<div class="time">
{{request.responseResult.responseTime}}
</div>
</el-col>
<el-col :span="2">
{{request.error}}
</el-col>
<el-col :span="2">
{{assertion}}
</el-col>
<el-col :span="2">
<el-tag size="mini" type="success" v-if="request.success">
{{$t('api_report.success')}}
</el-tag>
<el-tag size="mini" type="danger" v-else>
{{$t('api_report.fail')}}
</el-tag>
</el-col>
</el-row> </el-row>
</div> </div>
<el-collapse-transition> <el-collapse-transition>
@ -52,7 +29,6 @@
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<div v-else> <div v-else>
<ms-request-metric :request="request"/>
<ms-request-text v-if="isCodeEditAlive" :request="request"/> <ms-request-text v-if="isCodeEditAlive" :request="request"/>
<br> <br>
<ms-response-text :request-type="requestType" v-if="isCodeEditAlive" :response="request.responseResult"/> <ms-response-text :request-type="requestType" v-if="isCodeEditAlive" :response="request.responseResult"/>

View File

@ -32,6 +32,7 @@
methods: { methods: {
setFiles(item, bodyUploadFiles, obj) { setFiles(item, bodyUploadFiles, obj) {
if (item.body) { if (item.body) {
if (item.body.kvs) {
item.body.kvs.forEach(param => { item.body.kvs.forEach(param => {
if (param.files) { if (param.files) {
param.files.forEach(item => { param.files.forEach(item => {
@ -47,6 +48,8 @@
}); });
} }
}); });
}
if (item.body.binary) {
item.body.binary.forEach(param => { item.body.binary.forEach(param => {
if (param.files) { if (param.files) {
param.files.forEach(item => { param.files.forEach(item => {
@ -63,6 +66,7 @@
} }
}); });
} }
}
}, },
recursiveFile(arr, bodyUploadFiles, obj) { recursiveFile(arr, bodyUploadFiles, obj) {
arr.forEach(item => { arr.forEach(item => {

View File

@ -113,7 +113,7 @@
</el-form> </el-form>
<!-- 场景步骤--> <!-- 场景步骤-->
<div v-loading="isReloadData"> <div v-loading="loading">
<p class="tip">{{$t('api_test.automation.scenario_step')}} </p> <p class="tip">{{$t('api_test.automation.scenario_step')}} </p>
<el-row> <el-row>
<el-col :span="21"> <el-col :span="21">
@ -157,13 +157,13 @@
</el-row> </el-row>
</div> </div>
<!-- 场景步骤内容 --> <!-- 场景步骤内容 -->
<div style="margin-top: 10px" v-loading="isReloadData"> <div style="margin-top: 10px" v-loading="loading">
<el-tree node-key="resourceId" :props="props" :data="scenarioDefinition" <el-tree node-key="resourceId" :props="props" :data="scenarioDefinition"
:default-expanded-keys="expandedNode" :default-expanded-keys="expandedNode"
:expand-on-click-node="false" :expand-on-click-node="false"
@node-expand="nodeExpand" @node-expand="nodeExpand"
@node-collapse="nodeCollapse" @node-collapse="nodeCollapse"
:allow-drop="allowDrop" @node-drag-end="allowDrag" @node-click="nodeClick" v-if="!isReloadData" draggable> :allow-drop="allowDrop" @node-drag-end="allowDrag" @node-click="nodeClick" v-if="!loading" draggable>
<span class="custom-tree-node father" slot-scope="{ node, data}" style="width: 96%"> <span class="custom-tree-node father" slot-scope="{ node, data}" style="width: 96%">
<template> <template>
<!-- 场景 --> <!-- 场景 -->
@ -267,6 +267,8 @@
<!--场景公共参数--> <!--场景公共参数-->
<ms-scenario-parameters :currentScenario="currentScenario" @addParameters="addParameters" ref="scenarioParameters"/> <ms-scenario-parameters :currentScenario="currentScenario" @addParameters="addParameters" ref="scenarioParameters"/>
<!--外部导入-->
<api-import ref="apiImport" :saved="false" @refresh="apiImport"/>
</div> </div>
</el-card> </el-card>
</template> </template>
@ -293,6 +295,7 @@
import MsApiScenarioComponent from "./ApiScenarioComponent"; import MsApiScenarioComponent from "./ApiScenarioComponent";
import MsApiReportDetail from "../report/ApiReportDetail"; import MsApiReportDetail from "../report/ApiReportDetail";
import MsScenarioParameters from "./ScenarioParameters"; import MsScenarioParameters from "./ScenarioParameters";
import ApiImport from "../../definition/components/import/ApiImport";
export default { export default {
name: "EditApiScenario", name: "EditApiScenario",
@ -301,13 +304,21 @@
currentScenario: {}, currentScenario: {},
}, },
components: { components: {
ApiEnvironmentConfig, MsScenarioParameters, ApiEnvironmentConfig,
MsApiReportDetail, MsAddTag, MsRun, MsScenarioParameters,
MsApiScenarioComponent, MsImportApiScenario, MsApiReportDetail,
MsJsr233Processor, MsConstantTimer, MsAddTag, MsRun,
MsIfController, MsApiAssertions, MsApiScenarioComponent,
MsApiExtract, MsApiDefinition, MsImportApiScenario,
MsApiComponent, MsApiCustomize MsJsr233Processor,
MsConstantTimer,
MsIfController,
MsApiAssertions,
MsApiExtract,
MsApiDefinition,
MsApiComponent,
MsApiCustomize,
ApiImport,
}, },
data() { data() {
return { return {
@ -333,7 +344,7 @@
options: API_STATUS, options: API_STATUS,
levels: PRIORITY, levels: PRIORITY,
scenario: {}, scenario: {},
isReloadData: false, loading: false,
apiListVisible: false, apiListVisible: false,
customizeVisible: false, customizeVisible: false,
scenarioVisible: false, scenarioVisible: false,
@ -402,6 +413,7 @@
this.scenarioVisible = true; this.scenarioVisible = true;
break; break;
default: default:
this.$refs.apiImport.open();
break; break;
} }
this.sort(); this.sort();
@ -553,9 +565,9 @@
this.reload(); this.reload();
}, },
reload() { reload() {
this.isReloadData = true this.loading = true
this.$nextTick(() => { this.$nextTick(() => {
this.isReloadData = false this.loading = false
}) })
}, },
runDebug() { runDebug() {
@ -622,6 +634,7 @@
}, },
setFiles(item, bodyUploadFiles, obj) { setFiles(item, bodyUploadFiles, obj) {
if (item.body) { if (item.body) {
if (item.body.kvs) {
item.body.kvs.forEach(param => { item.body.kvs.forEach(param => {
if (param.files) { if (param.files) {
param.files.forEach(item => { param.files.forEach(item => {
@ -637,6 +650,8 @@
}); });
} }
}); });
}
if (item.body.binary) {
item.body.binary.forEach(param => { item.body.binary.forEach(param => {
if (param.files) { if (param.files) {
param.files.forEach(item => { param.files.forEach(item => {
@ -653,6 +668,7 @@
} }
}); });
} }
}
}, },
recursiveFile(arr, bodyUploadFiles, obj) { recursiveFile(arr, bodyUploadFiles, obj) {
arr.forEach(item => { arr.forEach(item => {
@ -728,7 +744,7 @@
}, },
runRefresh() { runRefresh() {
this.debugVisible = true; this.debugVisible = true;
this.isReloadData = false; this.loading = false;
}, },
showScenarioParameters() { showScenarioParameters() {
this.$refs.scenarioParameters.open(this.currentScenario.variables); this.$refs.scenarioParameters.open(this.currentScenario.variables);
@ -736,6 +752,15 @@
addParameters(data) { addParameters(data) {
this.currentScenario.variables = data; this.currentScenario.variables = data;
this.reload(); this.reload();
},
apiImport(importData) {
if (importData && importData.data) {
importData.data.forEach(item => {
this.setApiParameter(item, "API", "Copy");
})
this.sort();
this.reload();
}
} }
} }
} }

View File

@ -237,7 +237,7 @@
if (!this.$refs.apiList[0].tableData) { if (!this.$refs.apiList[0].tableData) {
return; return;
} }
let obj = {protocol: this.currentProtocol, data: this.$refs.apiList[0].tableData} let obj = {projectName: getCurrentProjectID(), protocol: this.currentProtocol, data: this.$refs.apiList[0].tableData}
downloadFile("导出API.json", JSON.stringify(obj)); downloadFile("导出API.json", JSON.stringify(obj));
}, },
refresh(data) { refresh(data) {

View File

@ -0,0 +1,63 @@
<template>
<div>
<el-dialog
:title="$t('commons.batch_add')"
:visible.sync="dialogVisible"
width="60%"
class="batch-edit-dialog"
:destroy-on-close="true"
@close="handleClose">
<div>
<div>格式参数名,必填,参数值,备注 Accept-Encoding,必填,utf-8,编码</div>
<div style="height: 200px">
<ms-code-edit :enable-format="false" mode="text" :data.sync="parameters" theme="eclipse" :modes="['text']"
ref="codeEdit"/>
</div>
</div>
<template v-slot:footer>
<ms-dialog-footer
@cancel="dialogVisible = false"
@confirm="confirm()"/>
</template>
</el-dialog>
</div>
</template>
<script>
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
export default {
name: "BatchAddParameter",
components: {
MsDialogFooter,
MsCodeEdit
},
props: {},
data() {
return {
dialogVisible: false,
parameters: "",
}
},
methods: {
open() {
this.dialogVisible = true;
listenGoBack(this.handleClose);
},
handleClose() {
this.parameters = "";
removeGoBackListener(this.handleClose);
},
confirm() {
this.dialogVisible = false;
this.$emit("batchSave", this.parameters);
}
}
}
</script>
<style scoped>
</style>

View File

@ -25,6 +25,10 @@
{{ $t('api_test.definition.request.body_binary') }} {{ $t('api_test.definition.request.body_binary') }}
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
<el-row v-if="body.type == 'Form Data' || body.type == 'WWW_FORM'">
<el-link class="ms-el-link" @click="batchAdd"> {{$t("commons.batch_add")}}</el-link>
</el-row>
<ms-api-variable :is-read-only="isReadOnly" <ms-api-variable :is-read-only="isReadOnly"
:parameters="body.kvs" :parameters="body.kvs"
:isShowEnable="isShowEnable" :isShowEnable="isShowEnable"
@ -55,12 +59,14 @@
type="body" type="body"
v-if="body.type == 'BINARY'"/> v-if="body.type == 'BINARY'"/>
<batch-add-parameter @batchSave="batchSave" ref="batchAddParameter"/>
</div> </div>
</template> </template>
<script> <script>
import MsApiKeyValue from "../ApiKeyValue"; import MsApiKeyValue from "../ApiKeyValue";
import {BODY_FORMAT, BODY_TYPE, KeyValue} from "../../model/ApiTestModel"; import {BODY_TYPE, KeyValue} from "../../model/ApiTestModel";
import MsCodeEdit from "../../../../common/components/MsCodeEdit"; import MsCodeEdit from "../../../../common/components/MsCodeEdit";
import MsJsonCodeEdit from "../../../../common/components/MsJsonCodeEdit"; import MsJsonCodeEdit from "../../../../common/components/MsJsonCodeEdit";
@ -68,6 +74,7 @@
import MsApiVariable from "../ApiVariable"; import MsApiVariable from "../ApiVariable";
import MsApiBinaryVariable from "./ApiBinaryVariable"; import MsApiBinaryVariable from "./ApiBinaryVariable";
import MsApiFromUrlVariable from "./ApiFromUrlVariable"; import MsApiFromUrlVariable from "./ApiFromUrlVariable";
import BatchAddParameter from "../basis/BatchAddParameter";
export default { export default {
name: "MsApiBody", name: "MsApiBody",
@ -78,7 +85,8 @@
MsApiKeyValue, MsApiKeyValue,
MsApiBinaryVariable, MsApiBinaryVariable,
MsApiFromUrlVariable, MsApiFromUrlVariable,
MsJsonCodeEdit MsJsonCodeEdit,
BatchAddParameter
}, },
props: { props: {
body: {}, body: {},
@ -147,9 +155,29 @@
}, },
jsonError(e) { jsonError(e) {
this.$error(e); this.$error(e);
},
batchAdd() {
this.$refs.batchAddParameter.open();
},
batchSave(data) {
if (data) {
let params = data.split("\n");
let keyValues = [];
params.forEach(item => {
let line = item.split(/|,/);
let required = false;
if (line[1] === '必填' || line[1] === 'true') {
required = true;
}
keyValues.push(new KeyValue({name: line[0], required: required, value: line[2], description: line[3], type: "text", valid: false, file: false, encode: true, enable: true, contentType: "text/plain"}));
})
keyValues.forEach(item => {
this.body.kvs.unshift(item);
})
} }
}, },
},
created() { created() {
if (!this.body.type) { if (!this.body.type) {
this.body.type = BODY_TYPE.FORM_DATA; this.body.type = BODY_TYPE.FORM_DATA;
@ -187,4 +215,8 @@
margin-top: 15px; margin-top: 15px;
} }
.ms-el-link {
float: right;
margin-right: 45px;
}
</style> </style>

View File

@ -70,6 +70,12 @@
export default { export default {
name: "ApiImport", name: "ApiImport",
components: {MsDialogFooter}, components: {MsDialogFooter},
props: {
saved: {
type: Boolean,
default: true,
}
},
data() { data() {
return { return {
visible: false, visible: false,
@ -148,7 +154,7 @@
}, },
uploadValidate(file, fileList) { uploadValidate(file, fileList) {
let suffix = file.name.substring(file.name.lastIndexOf('.') + 1); let suffix = file.name.substring(file.name.lastIndexOf('.') + 1);
if (!this.selectedPlatform.suffixes.has(suffix)) { if (this.selectedPlatform.suffixes && !this.selectedPlatform.suffixes.has(suffix)) {
this.$warning(this.$t('api_test.api_import.suffixFormatErr')); this.$warning(this.$t('api_test.api_import.suffixFormatErr'));
return false; return false;
} }
@ -170,7 +176,7 @@
let res = response.data; let res = response.data;
this.$success(this.$t('test_track.case.import.success')); this.$success(this.$t('test_track.case.import.success'));
this.visible = false; this.visible = false;
this.$emit('refresh'); this.$emit('refresh', res);
}); });
} else { } else {
return false; return false;
@ -181,8 +187,11 @@
let param = {}; let param = {};
Object.assign(param, this.formData); Object.assign(param, this.formData);
param.platform = this.selectedPlatformValue; param.platform = this.selectedPlatformValue;
param.saved = this.saved;
if (this.currentModule) {
param.moduleId = this.currentModule.id; param.moduleId = this.currentModule.id;
param.modulePath = this.currentModule.path; param.modulePath = this.currentModule.path;
}
param.projectId = getCurrentProjectID(); param.projectId = getCurrentProjectID();
if (!this.swaggerUrlEable) { if (!this.swaggerUrlEable) {
param.swaggerUrl = undefined; param.swaggerUrl = undefined;

View File

@ -13,7 +13,6 @@
</div> </div>
</span> </span>
</el-tooltip> </el-tooltip>
<ms-api-key-value :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :suggestions="headerSuggestions" :items="headers"/> <ms-api-key-value :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :suggestions="headerSuggestions" :items="headers"/>
</el-tab-pane> </el-tab-pane>
@ -25,7 +24,9 @@
<div class="el-step__icon-inner">{{request.arguments.length-1}}</div> <div class="el-step__icon-inner">{{request.arguments.length-1}}</div>
</div></span> </div></span>
</el-tooltip> </el-tooltip>
<el-row>
<el-link class="ms-el-link" @click="batchAdd"> {{$t("commons.batch_add")}}</el-link>
</el-row>
<ms-api-variable :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :parameters="request.arguments"/> <ms-api-variable :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :parameters="request.arguments"/>
</el-tab-pane> </el-tab-pane>
@ -39,6 +40,9 @@
</div> </div>
</span> </span>
</el-tooltip> </el-tooltip>
<el-row>
<el-link class="ms-el-link" @click="batchAdd"> {{$t("commons.batch_add")}}</el-link>
</el-row>
<ms-api-variable :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :parameters="request.rest"/> <ms-api-variable :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :parameters="request.rest"/>
</el-tab-pane> </el-tab-pane>
@ -71,6 +75,9 @@
<!--提取规则--> <!--提取规则-->
<ms-api-extract :is-read-only="isReadOnly" @copyRow="copyRow" @remove="remove" v-if="row.type==='Extract'" :extract="row"/> <ms-api-extract :is-read-only="isReadOnly" @copyRow="copyRow" @remove="remove" v-if="row.type==='Extract'" :extract="row"/>
</div> </div>
<batch-add-parameter @batchSave="batchSave" ref="batchAddParameter"/>
</div> </div>
</el-col> </el-col>
<!--操作按钮--> <!--操作按钮-->
@ -98,15 +105,24 @@
import {createComponent} from "../../jmeter/components"; import {createComponent} from "../../jmeter/components";
import MsApiAssertions from "../../assertion/ApiAssertions"; import MsApiAssertions from "../../assertion/ApiAssertions";
import MsApiExtract from "../../extract/ApiExtract"; import MsApiExtract from "../../extract/ApiExtract";
import {Assertions, Body, Extract} from "../../../model/ApiTestModel"; import {Assertions, Body, Extract, KeyValue} from "../../../model/ApiTestModel";
import {getUUID} from "@/common/js/utils"; import {getUUID} from "@/common/js/utils";
import BatchAddParameter from "../../basis/BatchAddParameter";
export default { export default {
name: "MsApiHttpRequestForm", name: "MsApiHttpRequestForm",
components: { components: {
MsJsr233Processor, MsJsr233Processor,
MsApiAdvancedConfig, MsApiAdvancedConfig,
MsApiVariable, ApiRequestMethodSelect, MsApiExtract, MsApiAuthConfig, MsApiBody, MsApiKeyValue, MsApiAssertions BatchAddParameter,
MsApiVariable,
ApiRequestMethodSelect,
MsApiExtract,
MsApiAuthConfig,
MsApiBody,
MsApiKeyValue,
MsApiAssertions
}, },
props: { props: {
request: {}, request: {},
@ -149,7 +165,7 @@
}, },
headerSuggestions: REQUEST_HEADERS, headerSuggestions: REQUEST_HEADERS,
isReloadData: false, isReloadData: false,
isBodyShow: true isBodyShow: true,
} }
}, },
@ -216,6 +232,35 @@
this.$nextTick(() => { this.$nextTick(() => {
this.isBodyShow = true; this.isBodyShow = true;
}); });
},
batchAdd() {
this.$refs.batchAddParameter.open();
},
batchSave(data) {
if (data) {
let params = data.split("\n");
let keyValues = [];
params.forEach(item => {
let line = item.split(/|,/);
let required = false;
if (line[1] === '必填' || line[1] === 'true') {
required = true;
}
keyValues.push(new KeyValue({name: line[0], required: required, value: line[2], description: line[3], type: "text", valid: false, file: false, encode: true, enable: true, contentType: "text/plain"}));
})
keyValues.forEach(item => {
switch (this.activeName) {
case "parameters":
this.request.arguments.unshift(item);
break;
case "rest":
this.request.rest.unshift(item);
break;
default:
break;
}
})
}
} }
} }
} }
@ -273,4 +318,8 @@
border: #E6EEF2; border: #E6EEF2;
} }
.ms-el-link {
float: right;
margin-right: 45px;
}
</style> </style>

View File

@ -1,25 +1,10 @@
<template> <template>
<div id="menu-bar" v-if="isRouterAlive"> <div id="menu-bar" v-if="isRouterAlive">
<el-row type="flex"> <el-row type="flex">
<project-change :project-name="currentProject"/>
<el-col :span="14"> <el-col :span="14">
<el-menu class="header-menu" :unique-opened="true" mode="horizontal" router :default-active='$route.path'> <el-menu class="header-menu" :unique-opened="true" mode="horizontal" router :default-active='$route.path'>
<el-submenu :class="{'deactivation':!isProjectActivation}"
v-permission="['test_manager','test_user','test_viewer']" index="3">
<template v-slot:title>
<span style="display: inline-block;width: 150px;white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" :title="currentProject">
{{ $t('commons.project') }}: {{currentProject}}
</span>
</template>
<search-list ref="projectRecent" :options="projectRecent" :current-project.sync="currentProject"/>
<el-divider class="menu-divider"/>
<el-menu-item :index="'/setting/project/create'">
<font-awesome-icon :icon="['fa', 'plus']"/>
<span style="padding-left: 7px;">{{ $t("project.create") }}</span>
</el-menu-item>
<ms-show-all :index="'/setting/project/all'"/>
</el-submenu>
<el-menu-item :index="'/api/home'"> <el-menu-item :index="'/api/home'">
{{ $t("i18n.home") }} {{ $t("i18n.home") }}
</el-menu-item> </el-menu-item>
@ -75,22 +60,13 @@ import MsCreateButton from "../../common/head/CreateButton";
import MsCreateTest from "../../common/head/CreateTest"; import MsCreateTest from "../../common/head/CreateTest";
import {ApiEvent, LIST_CHANGE} from "@/business/components/common/head/ListEvent"; import {ApiEvent, LIST_CHANGE} from "@/business/components/common/head/ListEvent";
import SearchList from "@/business/components/common/head/SearchList"; import SearchList from "@/business/components/common/head/SearchList";
import ProjectChange from "@/business/components/common/head/ProjectSwitch";
export default { export default {
name: "MsApiHeaderMenus", name: "MsApiHeaderMenus",
components: {SearchList, MsCreateTest, MsCreateButton, MsShowAll, MsRecentList}, components: {SearchList, MsCreateTest, MsCreateButton, MsShowAll, MsRecentList, ProjectChange},
data() { data() {
return { return {
projectRecent: {
title: this.$t('project.recent'),
url: "/project/recent/5",
index: function (item) {
return '/api/test/list/' + item.id;
},
router: function (item) {
return {name: 'ApiTestList', params: {projectId: item.id, projectName: item.name}}
}
},
testRecent: { testRecent: {
title: this.$t('load_test.recent'), title: this.$t('load_test.recent'),
url: "/api/recent/5", url: "/api/recent/5",
@ -115,19 +91,10 @@ export default {
currentProject: '' currentProject: ''
} }
}, },
// watch: {
// '$route'(to) {
// this.init();
// },
// },
methods: { methods: {
registerEvents() { registerEvents() {
ApiEvent.$on(LIST_CHANGE, () => { ApiEvent.$on(LIST_CHANGE, () => {
// todo refs // // todo refs
if (!this.$refs.projectRecent) {
return;
}
this.$refs.projectRecent.recent();
this.$refs.testRecent.recent(); this.$refs.testRecent.recent();
this.$refs.reportRecent.recent(); this.$refs.reportRecent.recent();
}); });
@ -138,17 +105,6 @@ export default {
this.isRouterAlive = true; this.isRouterAlive = true;
}); });
}, },
// init() {
// let path = this.$route.path;
// if (path.indexOf("/api/test/list") >= 0 && !!this.$route.params.projectId) {
// this.apiTestProjectPath = path;
// //
// this.isProjectActivation = false;
// this.reload();
// } else {
// this.isProjectActivation = true;
// }
// },
}, },
mounted() { mounted() {
this.registerEvents(); this.registerEvents();

View File

@ -1,28 +0,0 @@
<template>
<span>
<el-submenu index="10">
<template v-slot:title>操作</template>
<el-input
placeholder="请输入内容"
prefix-icon="el-icon-search"
v-model="input2">
</el-input>
</el-submenu>
</span>
</template>
<script>
export default {
name: "ProjectMenu",
data() {
return {
input2: '1'
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,54 @@
<template>
<el-menu class="header-menu" :unique-opened="true" mode="horizontal" default-active="1" router>
<!-- 不激活项目路由-->
<el-menu-item index="1" v-show="false">Placeholder</el-menu-item>
<el-submenu v-permission="['test_manager','test_user','test_viewer']" index="2" popper-class="submenu">
<template v-slot:title>
<span class="project-name" :title="currentProject">
{{ $t('commons.project') }}: {{currentProject}}
</span>
</template>
<search-list :current-project.sync="currentProject"/>
<el-divider/>
<el-menu-item :index="'/setting/project/create'">
<font-awesome-icon :icon="['fa', 'plus']"/>
<span style="padding-left: 7px;">{{ $t("project.create") }}</span>
</el-menu-item>
<el-menu-item :index="'/setting/project/all'">
<font-awesome-icon :icon="['fa', 'list-ul']"/>
<span style="padding-left: 7px;">{{ $t('commons.show_all') }}</span>
</el-menu-item>
</el-submenu>
</el-menu>
</template>
<script>
import SearchList from "@/business/components/common/head/SearchList";
export default {
name: "ProjectSwitch",
props: {
projectName: String
},
components: {SearchList},
data() {
return {
currentProject: this.projectName
}
}
}
</script>
<style scoped>
.project-name {
display: inline-block;
width: 130px;
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
}
.el-divider--horizontal {
margin: 0;
}
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div v-loading="result.loading" class="search-list"> <div v-loading="result.loading">
<el-input placeholder="搜索项目" <el-input placeholder="搜索项目"
prefix-icon="el-icon-search" prefix-icon="el-icon-search"
v-model="searchString" v-model="searchString"
@ -76,13 +76,6 @@ export default {
}) })
} }
}, },
search() {
if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
this.result = this.$post("/project/search", {name: this.searchString},response => {
this.items = response.data;
})
}
},
query(queryString) { query(queryString) {
this.items = queryString ? this.searchArray.filter(this.createFilter(queryString)) : this.searchArray; this.items = queryString ? this.searchArray.filter(this.createFilter(queryString)) : this.searchArray;
}, },
@ -98,13 +91,10 @@ export default {
} }
this.$post("/user/update/current", {id: this.userId, lastProjectId: projectId}, () => { this.$post("/user/update/current", {id: this.userId, lastProjectId: projectId}, () => {
localStorage.setItem(PROJECT_ID, projectId); localStorage.setItem(PROJECT_ID, projectId);
if (this.$route.path.indexOf('/track/review/view/') >= 0) { let path = this.$route.matched[0].path ? this.$route.matched[0].path : '/';
this.$router.replace('/track/review/all'); this.$router.push(path).then(() => {
} else if (this.$route.path.indexOf('/track/plan/view/') >= 0) { window.location.reload()
this.$router.replace('/track/plan/all'); }).catch(err => err);
} else {
window.location.reload();
}
this.changeProjectName(projectId); this.changeProjectName(projectId);
}); });
}, },
@ -135,7 +125,7 @@ export default {
.title { .title {
display: inline-block; display: inline-block;
padding-left: 20px; padding-left: 15px;
max-width: 200px; max-width: 200px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;

View File

@ -1,25 +1,9 @@
<template> <template>
<div id="menu-bar"> <div id="menu-bar">
<el-row type="flex"> <el-row type="flex">
<el-col :span="10"> <project-change :project-name="currentProject"/>
<el-col :span="12">
<el-menu class="header-menu" :unique-opened="true" mode="horizontal" router :default-active='$route.path'> <el-menu class="header-menu" :unique-opened="true" mode="horizontal" router :default-active='$route.path'>
<el-submenu v-permission="['test_manager','test_user','test_viewer']"
index="3" popper-class="submenu">
<template v-slot:title>
<span style="display: inline-block;width: 150px;white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" :title="currentProject">
{{ $t('commons.project') }}: {{currentProject}}
</span>
</template>
<search-list ref="projectRecent" :options="projectRecent" :current-project.sync="currentProject"/>
<el-divider/>
<el-menu-item :index="'/setting/project/create'">
<font-awesome-icon :icon="['fa', 'plus']"/>
<span style="padding-left: 7px;">{{ $t("project.create") }}</span>
</el-menu-item>
<ms-show-all :index="'/setting/project/all'"/>
</el-submenu>
<el-menu-item :index="'/performance/home'"> <el-menu-item :index="'/performance/home'">
{{ $t("i18n.home") }} {{ $t("i18n.home") }}
</el-menu-item> </el-menu-item>
@ -61,10 +45,12 @@ import MsCreateButton from "../../common/head/CreateButton";
import MsShowAll from "../../common/head/ShowAll"; import MsShowAll from "../../common/head/ShowAll";
import {LIST_CHANGE, PerformanceEvent} from "@/business/components/common/head/ListEvent"; import {LIST_CHANGE, PerformanceEvent} from "@/business/components/common/head/ListEvent";
import SearchList from "@/business/components/common/head/SearchList"; import SearchList from "@/business/components/common/head/SearchList";
import ProjectChange from "@/business/components/common/head/ProjectSwitch";
export default { export default {
name: "PerformanceHeaderMenus", name: "PerformanceHeaderMenus",
components: { components: {
ProjectChange,
SearchList, SearchList,
MsCreateButton, MsCreateButton,
MsShowAll, MsShowAll,
@ -73,16 +59,6 @@ export default {
}, },
data() { data() {
return { return {
projectRecent: {
title: this.$t('project.recent'),
url: "/project/recent/5",
index(item) {
return '/performance/test/' + item.id;
},
router(item) {
return {name: 'perPlan', params: {projectId: item.id, projectName: item.name}}
}
},
testRecent: { testRecent: {
title: this.$t('load_test.recent'), title: this.$t('load_test.recent'),
url: "/performance/recent/5", url: "/performance/recent/5",
@ -108,11 +84,7 @@ export default {
methods: { methods: {
registerEvents() { registerEvents() {
PerformanceEvent.$on(LIST_CHANGE, () => { PerformanceEvent.$on(LIST_CHANGE, () => {
// todo refs // // todo refs
if (!this.$refs.projectRecent) {
return;
}
this.$refs.projectRecent.recent();
this.$refs.testRecent.recent(); this.$refs.testRecent.recent();
this.$refs.reportRecent.recent(); this.$refs.reportRecent.recent();
}); });

View File

@ -2,25 +2,10 @@
<div id="menu-bar" v-if="isRouterAlive"> <div id="menu-bar" v-if="isRouterAlive">
<el-row type="flex"> <el-row type="flex">
<el-col :span="16"> <project-change :project-name="currentProject"/>
<el-col :span="14">
<el-menu class="header-menu" :unique-opened="true" mode="horizontal" router <el-menu class="header-menu" :unique-opened="true" mode="horizontal" router
:default-active='$route.path'> :default-active='$route.path'>
<el-submenu :class="{'deactivation':!isProjectActivation}"
v-permission="['test_manager','test_user','test_viewer']" index="3" popper-class="submenu">
<template v-slot:title>
<span style="display: inline-block;width: 150px;white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" :title="currentProject">
{{ $t('commons.project') }}: {{currentProject}}
</span>
</template>
<search-list ref="projectRecent" :options="projectRecent" :current-project.sync="currentProject"/>
<el-divider/>
<el-menu-item :index="'/setting/project/create'">
<font-awesome-icon :icon="['fa', 'plus']"/>
<span style="padding-left: 7px;">{{ $t("project.create") }}</span>
</el-menu-item>
<ms-show-all :index="'/setting/project/all'"/>
</el-submenu>
<el-menu-item :index="'/track/home'"> <el-menu-item :index="'/track/home'">
{{ $t("i18n.home") }} {{ $t("i18n.home") }}
</el-menu-item> </el-menu-item>
@ -70,10 +55,11 @@ import MsRecentList from "../../common/head/RecentList";
import MsCreateButton from "../../common/head/CreateButton"; import MsCreateButton from "../../common/head/CreateButton";
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent"; import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
import SearchList from "@/business/components/common/head/SearchList"; import SearchList from "@/business/components/common/head/SearchList";
import ProjectChange from "@/business/components/common/head/ProjectSwitch";
export default { export default {
name: "TrackHeaderMenus", name: "TrackHeaderMenus",
components: {SearchList, MsShowAll, MsRecentList, MsCreateButton}, components: {ProjectChange, SearchList, MsShowAll, MsRecentList, MsCreateButton},
data() { data() {
return { return {
testPlanViewPath: '', testPlanViewPath: '',
@ -83,16 +69,6 @@ export default {
testCaseProjectPath: '', testCaseProjectPath: '',
isProjectActivation: true, isProjectActivation: true,
currentProject: '', currentProject: '',
projectRecent: {
title: this.$t('project.recent'),
url: "/project/recent/5",
index: function (item) {
return '/track/case/' + item.id;
},
router: function (item) {
return {name: 'testCase', params: {projectId: item.id, projectName: item.name}}
}
},
caseRecent: { caseRecent: {
title: this.$t('test_track.recent_case'), title: this.$t('test_track.recent_case'),
url: "/test/case/recent/5", url: "/test/case/recent/5",
@ -140,14 +116,6 @@ export default {
}, },
init() { init() {
let path = this.$route.path; let path = this.$route.path;
// if (path.indexOf("/track/case") >= 0 && !!this.$route.params.projectId) {
// this.testCaseProjectPath = path;
// //
// this.isProjectActivation = false;
// this.reload();
// } else {
// this.isProjectActivation = true;
// }
if (path.indexOf("/track/plan/view") >= 0) { if (path.indexOf("/track/plan/view") >= 0) {
this.testPlanViewPath = path; this.testPlanViewPath = path;
this.reload(); this.reload();
@ -163,11 +131,7 @@ export default {
}, },
registerEvents() { registerEvents() {
TrackEvent.$on(LIST_CHANGE, () => { TrackEvent.$on(LIST_CHANGE, () => {
// todo refs // // todo refs
if (!this.$refs.projectRecent) {
return;
}
this.$refs.projectRecent.recent();
this.$refs.planRecent.recent(); this.$refs.planRecent.recent();
this.$refs.caseRecent.recent(); this.$refs.caseRecent.recent();
}); });
@ -199,4 +163,11 @@ export default {
border-bottom: white !important; border-bottom: white !important;
} }
/*.project-change {*/
/* height: 40px;*/
/* line-height: 40px;*/
/* color: inherit;*/
/* margin-left: 20px;*/
/*}*/
</style> </style>

View File

@ -124,6 +124,7 @@ export default {
already_exists: 'The name already exists', already_exists: 'The name already exists',
modifier: 'Modifier', modifier: 'Modifier',
validate: "Validate", validate: "Validate",
batch_add: "Batch add",
date: { date: {
select_date: 'Select date', select_date: 'Select date',
start_date: 'Start date', start_date: 'Start date',

View File

@ -124,6 +124,7 @@ export default {
already_exists: '名称不能重复', already_exists: '名称不能重复',
modifier: '修改人', modifier: '修改人',
validate: "校验", validate: "校验",
batch_add: "批量添加",
date: { date: {
select_date: '选择日期', select_date: '选择日期',
start_date: '开始日期', start_date: '开始日期',
@ -589,7 +590,7 @@ export default {
select_table: "选择可见数据", select_table: "选择可见数据",
select_all: "选择全部数据" select_all: "选择全部数据"
}, },
report_name_info: '请输入报名称', report_name_info: '请输入报名称',
save_case_info: '请先保存用例', save_case_info: '请先保存用例',
reference_deleted: '引用已删除', reference_deleted: '引用已删除',
}, },

View File

@ -124,6 +124,7 @@ export default {
already_exists: '名稱不能重復', already_exists: '名稱不能重復',
modifier: '修改人', modifier: '修改人',
validate: "校驗", validate: "校驗",
batch_add: "批量添加",
date: { date: {
select_date: '選擇日期', select_date: '選擇日期',
start_date: '開始日期', start_date: '開始日期',
@ -166,7 +167,7 @@ export default {
current_user: "是當前用戶" current_user: "是當前用戶"
} }
}, },
monitor:"監控", monitor: "監控",
all_label: { all_label: {
case: "全部用例", case: "全部用例",
review: "全部評審" review: "全部評審"
@ -589,7 +590,7 @@ export default {
select_table: "選擇可見數據", select_table: "選擇可見數據",
select_all: "選擇全部數據" select_all: "選擇全部數據"
}, },
report_name_info: '請輸入報名稱', report_name_info: '請輸入報名稱',
save_case_info: '請先保存用例', save_case_info: '請先保存用例',
reference_deleted: '引用已删除', reference_deleted: '引用已删除',
}, },
@ -829,14 +830,14 @@ export default {
not_exist: "測試報告不存在", not_exist: "測試報告不存在",
}, },
api_monitor: { api_monitor: {
to:"到", to: "到",
start_time:"開始時間", start_time: "開始時間",
end_time:"結束時間", end_time: "結束時間",
today:"今天", today: "今天",
this_week:"本週", this_week: "本週",
this_mouth:"本月", this_mouth: "本月",
please_search:"請搜索", please_search: "請搜索",
date:"日期" date: "日期"
}, },
test_track: { test_track: {
test_track: "測試跟蹤", test_track: "測試跟蹤",