Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
MeterSphere
metersphere
提交
22be52a5
M
metersphere
项目概览
MeterSphere
/
metersphere
上一次同步 大约 3 年
通知
25
Star
1
Fork
1
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
M
metersphere
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
22be52a5
编写于
4月 17, 2020
作者:
C
Captain.B
浏览文件
操作
浏览文件
下载
差异文件
Merge remote-tracking branch 'origin/dev' into dev
上级
eb4bfcc0
d2817334
变更
33
隐藏空白更改
内联
并排
Showing
33 changed file
with
1163 addition
and
422 deletion
+1163
-422
backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseMapper.java
...ava/io/metersphere/base/mapper/ext/ExtTestCaseMapper.java
+2
-1
backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseMapper.xml
...java/io/metersphere/base/mapper/ext/ExtTestCaseMapper.xml
+3
-0
backend/src/main/java/io/metersphere/controller/TestPlanTestCaseController.java
...io/metersphere/controller/TestPlanTestCaseController.java
+8
-3
backend/src/main/java/io/metersphere/controller/request/testcase/TestCaseBatchRequest.java
...ere/controller/request/testcase/TestCaseBatchRequest.java
+19
-0
backend/src/main/java/io/metersphere/controller/request/testplancase/QueryTestPlanCaseRequest.java
...roller/request/testplancase/QueryTestPlanCaseRequest.java
+39
-0
backend/src/main/java/io/metersphere/report/JtlResolver.java
backend/src/main/java/io/metersphere/report/JtlResolver.java
+34
-119
backend/src/main/java/io/metersphere/service/TestCaseService.java
...src/main/java/io/metersphere/service/TestCaseService.java
+1
-1
backend/src/main/java/io/metersphere/service/TestPlanTestCaseService.java
.../java/io/metersphere/service/TestPlanTestCaseService.java
+17
-2
backend/src/main/resources/i18n/messages_zh_CN.properties
backend/src/main/resources/i18n/messages_zh_CN.properties
+21
-21
frontend/src/business/components/api/test/ApiScenarioConfig.vue
...nd/src/business/components/api/test/ApiScenarioConfig.vue
+29
-25
frontend/src/business/components/api/test/components/ApiAssertionRegex.vue
...ness/components/api/test/components/ApiAssertionRegex.vue
+64
-0
frontend/src/business/components/api/test/components/ApiAssertionResponseTime.vue
...mponents/api/test/components/ApiAssertionResponseTime.vue
+51
-0
frontend/src/business/components/api/test/components/ApiAssertionText.vue
...iness/components/api/test/components/ApiAssertionText.vue
+95
-0
frontend/src/business/components/api/test/components/ApiAssertions.vue
...business/components/api/test/components/ApiAssertions.vue
+59
-0
frontend/src/business/components/api/test/components/ApiAssertionsEdit.vue
...ness/components/api/test/components/ApiAssertionsEdit.vue
+57
-0
frontend/src/business/components/api/test/components/ApiBody.vue
...d/src/business/components/api/test/components/ApiBody.vue
+11
-8
frontend/src/business/components/api/test/components/ApiKeyValue.vue
...c/business/components/api/test/components/ApiKeyValue.vue
+13
-10
frontend/src/business/components/api/test/components/ApiRequest.vue
...rc/business/components/api/test/components/ApiRequest.vue
+35
-48
frontend/src/business/components/api/test/components/ApiRequestForm.vue
...usiness/components/api/test/components/ApiRequestForm.vue
+16
-10
frontend/src/business/components/api/test/components/ApiScenarioForm.vue
...siness/components/api/test/components/ApiScenarioForm.vue
+13
-11
frontend/src/business/components/api/test/model/APIModel.js
frontend/src/business/components/api/test/model/APIModel.js
+123
-0
frontend/src/business/components/common/components/MsDialogFooter.vue
.../business/components/common/components/MsDialogFooter.vue
+26
-0
frontend/src/business/components/common/components/MsTipButton.vue
...src/business/components/common/components/MsTipButton.vue
+54
-0
frontend/src/business/components/performance/report/components/TestOverview.vue
...components/performance/report/components/TestOverview.vue
+26
-12
frontend/src/business/components/track/case/components/TestCaseList.vue
...usiness/components/track/case/components/TestCaseList.vue
+5
-33
frontend/src/business/components/track/plan/common/TestPlanTestCaseStatusButton.vue
...onents/track/plan/common/TestPlanTestCaseStatusButton.vue
+51
-0
frontend/src/business/components/track/plan/components/ExecutorEdit.vue
...usiness/components/track/plan/components/ExecutorEdit.vue
+74
-0
frontend/src/business/components/track/plan/components/StatusEdit.vue
.../business/components/track/plan/components/StatusEdit.vue
+68
-0
frontend/src/business/components/track/plan/components/TestCaseRelevance.vue
...ss/components/track/plan/components/TestCaseRelevance.vue
+0
-1
frontend/src/business/components/track/plan/components/TestPlanList.vue
...usiness/components/track/plan/components/TestPlanList.vue
+23
-36
frontend/src/business/components/track/plan/components/TestPlanTestCaseEdit.vue
...components/track/plan/components/TestPlanTestCaseEdit.vue
+15
-27
frontend/src/business/components/track/plan/components/TestPlanTestCaseList.vue
...components/track/plan/components/TestPlanTestCaseList.vue
+87
-49
frontend/src/i18n/zh-CN.js
frontend/src/i18n/zh-CN.js
+24
-5
未找到文件。
backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseMapper.java
浏览文件 @
22be52a5
...
...
@@ -3,6 +3,7 @@ package io.metersphere.base.mapper.ext;
import
io.metersphere.base.domain.TestCase
;
import
io.metersphere.controller.request.ReportRequest
;
import
io.metersphere.controller.request.testcase.QueryTestCaseRequest
;
import
io.metersphere.controller.request.testplancase.QueryTestPlanCaseRequest
;
import
io.metersphere.dto.ReportDTO
;
import
io.metersphere.dto.TestPlanCaseDTO
;
import
org.apache.ibatis.annotations.Param
;
...
...
@@ -13,5 +14,5 @@ public interface ExtTestCaseMapper {
List
<
TestCase
>
getTestCaseNames
(
@Param
(
"request"
)
QueryTestCaseRequest
request
);
List
<
TestPlanCaseDTO
>
getTestPlanTestCases
(
@Param
(
"request"
)
QueryTestCaseRequest
request
);
List
<
TestPlanCaseDTO
>
getTestPlanTestCases
(
@Param
(
"request"
)
QueryTest
Plan
CaseRequest
request
);
}
backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseMapper.xml
浏览文件 @
22be52a5
...
...
@@ -37,5 +37,8 @@
<if
test=
"request.name != null"
>
and t2.name like CONCAT('%', #{request.name},'%')
</if>
<if
test=
"request.executor != null"
>
and t1.executor = #{request.executor}
</if>
</select>
</mapper>
\ No newline at end of file
backend/src/main/java/io/metersphere/controller/TestPlanTestCaseController.java
浏览文件 @
22be52a5
...
...
@@ -7,6 +7,8 @@ import io.metersphere.base.domain.TestPlanTestCase;
import
io.metersphere.commons.utils.PageUtils
;
import
io.metersphere.commons.utils.Pager
;
import
io.metersphere.controller.request.testcase.QueryTestCaseRequest
;
import
io.metersphere.controller.request.testcase.TestCaseBatchRequest
;
import
io.metersphere.controller.request.testplancase.QueryTestPlanCaseRequest
;
import
io.metersphere.dto.TestPlanCaseDTO
;
import
io.metersphere.service.TestPlanTestCaseService
;
import
org.springframework.web.bind.annotation.*
;
...
...
@@ -22,7 +24,7 @@ public class TestPlanTestCaseController {
TestPlanTestCaseService
testPlanTestCaseService
;
@PostMapping
(
"/list/{goPage}/{pageSize}"
)
public
Pager
<
List
<
TestPlanCaseDTO
>>
getTestPlanCases
(
@PathVariable
int
goPage
,
@PathVariable
int
pageSize
,
@RequestBody
QueryTestCaseRequest
request
){
public
Pager
<
List
<
TestPlanCaseDTO
>>
getTestPlanCases
(
@PathVariable
int
goPage
,
@PathVariable
int
pageSize
,
@RequestBody
QueryTest
Plan
CaseRequest
request
){
Page
<
Object
>
page
=
PageHelper
.
startPage
(
goPage
,
pageSize
,
true
);
return
PageUtils
.
setPageInfo
(
page
,
testPlanTestCaseService
.
getTestPlanCases
(
request
));
}
...
...
@@ -32,11 +34,14 @@ public class TestPlanTestCaseController {
testPlanTestCaseService
.
editTestCase
(
testPlanTestCase
);
}
@PostMapping
(
"/batch/edit"
)
public
void
editTestCaseBath
(
@RequestBody
TestCaseBatchRequest
request
){
testPlanTestCaseService
.
editTestCaseBath
(
request
);
}
@PostMapping
(
"/delete/{id}"
)
public
int
deleteTestCase
(
@PathVariable
Integer
id
){
return
testPlanTestCaseService
.
deleteTestCase
(
id
);
}
}
backend/src/main/java/io/metersphere/controller/request/testcase/TestCaseBatchRequest.java
0 → 100644
浏览文件 @
22be52a5
package
io.metersphere.controller.request.testcase
;
import
io.metersphere.base.domain.TestCase
;
import
io.metersphere.base.domain.TestPlanTestCase
;
import
java.util.List
;
public
class
TestCaseBatchRequest
extends
TestPlanTestCase
{
private
List
<
Integer
>
ids
;
public
List
<
Integer
>
getIds
()
{
return
ids
;
}
public
void
setIds
(
List
<
Integer
>
ids
)
{
this
.
ids
=
ids
;
}
}
backend/src/main/java/io/metersphere/controller/request/testplancase/QueryTestPlanCaseRequest.java
0 → 100644
浏览文件 @
22be52a5
package
io.metersphere.controller.request.testplancase
;
import
io.metersphere.base.domain.TestCase
;
import
io.metersphere.base.domain.TestPlanTestCase
;
import
java.util.List
;
public
class
QueryTestPlanCaseRequest
extends
TestPlanTestCase
{
private
List
<
Integer
>
nodeIds
;
private
String
workspaceId
;
private
String
name
;
public
String
getName
()
{
return
name
;
}
public
void
setName
(
String
name
)
{
this
.
name
=
name
;
}
public
List
<
Integer
>
getNodeIds
()
{
return
nodeIds
;
}
public
void
setNodeIds
(
List
<
Integer
>
nodeIds
)
{
this
.
nodeIds
=
nodeIds
;
}
public
String
getWorkspaceId
()
{
return
workspaceId
;
}
public
void
setWorkspaceId
(
String
workspaceId
)
{
this
.
workspaceId
=
workspaceId
;
}
}
backend/src/main/java/io/metersphere/report/JtlResolver.java
浏览文件 @
22be52a5
...
...
@@ -11,10 +11,7 @@ import org.apache.commons.lang3.StringUtils;
import
org.apache.jmeter.report.core.Sample
;
import
org.apache.jmeter.report.core.SampleMetadata
;
import
org.apache.jmeter.report.dashboard.JsonizerVisitor
;
import
org.apache.jmeter.report.processor.ListResultData
;
import
org.apache.jmeter.report.processor.MapResultData
;
import
org.apache.jmeter.report.processor.ResultData
;
import
org.apache.jmeter.report.processor.SampleContext
;
import
org.apache.jmeter.report.processor.*
;
import
org.apache.jmeter.report.processor.graph.AbstractOverTimeGraphConsumer
;
import
org.apache.jmeter.report.processor.graph.impl.ActiveThreadsGraphConsumer
;
import
org.apache.jmeter.report.processor.graph.impl.HitsPerSecondGraphConsumer
;
...
...
@@ -345,61 +342,6 @@ public class JtlResolver {
return
testOverview
;
}
// public static List<ChartsData> getLoadChartData(String jtlString) {
// List<ChartsData> chartsDataList = new ArrayList<>();
// List<Metric> totalMetricList = JtlResolver.resolver(jtlString);
//
// if (totalMetricList != null) {
// for (Metric metric : totalMetricList) {
// metric.setTimestamp(stampToDate(DATE_TIME_PATTERN, metric.getTimestamp()));
// }
// }
// Map<String, List<Metric>> collect = Objects.requireNonNull(totalMetricList).stream().collect(Collectors.groupingBy(Metric::getTimestamp));
// List<Map.Entry<String, List<Metric>>> entries = new ArrayList<>(collect.entrySet());
//
// for (Map.Entry<String, List<Metric>> entry : entries) {
// int failSize = 0;
// List<Metric> metrics = entry.getValue();
// Map<String, List<Metric>> metricsMap = metrics.stream().collect(Collectors.groupingBy(Metric::getThreadName));
// int maxUsers = metricsMap.size();
// for (Metric metric : metrics) {
// String isSuccess = metric.getSuccess();
// if (!"true".equals(isSuccess)) {
// failSize++;
// }
// }
//
// String timeStamp = "";
// try {
// timeStamp = formatDate(entry.getKey());
// } catch (ParseException e) {
// e.printStackTrace();
// }
//
// ChartsData chartsData = new ChartsData();
// chartsData.setxAxis(timeStamp);
// chartsData.setGroupName("hits");
// chartsData.setyAxis(new BigDecimal(metrics.size() * 1.0 / maxUsers));
// chartsDataList.add(chartsData);
//
// chartsData = new ChartsData();
// chartsData.setxAxis(timeStamp);
// chartsData.setGroupName("users");
// chartsData.setyAxis(new BigDecimal(maxUsers));
// chartsDataList.add(chartsData);
//
// chartsData = new ChartsData();
// chartsData.setxAxis(timeStamp);
// chartsData.setGroupName("errors");
// chartsData.setyAxis(new BigDecimal(failSize));
// chartsDataList.add(chartsData);
//
// }
//
// return chartsDataList;
// }
public
static
List
<
ChartsData
>
getLoadChartData
(
String
jtlString
)
{
Map
<
String
,
Object
>
activeThreadMap
=
getResultDataMap
(
jtlString
,
new
ActiveThreadsGraphConsumer
());
Map
<
String
,
Object
>
hitsMap
=
getResultDataMap
(
jtlString
,
new
HitsPerSecondGraphConsumer
());
...
...
@@ -411,45 +353,6 @@ public class JtlResolver {
return
activeThreadList
;
}
// public static List<ChartsData> getResponseTimeChartData(String jtlString) {
// List<ChartsData> chartsDataList = new ArrayList<>();
// List<Metric> totalMetricList = JtlResolver.resolver(jtlString);
//
// totalMetricList.forEach(metric -> {
// metric.setTimestamp(stampToDate(DATE_TIME_PATTERN, metric.getTimestamp()));
// });
//
// Map<String, List<Metric>> metricMap = totalMetricList.stream().collect(Collectors.groupingBy(Metric::getTimestamp));
// List<Map.Entry<String, List<Metric>>> entries = new ArrayList<>(metricMap.entrySet());
//
// for (Map.Entry<String, List<Metric>> entry : entries) {
// List<Metric> metricList = entry.getValue();
// Map<String, List<Metric>> metricsMap = metricList.stream().collect(Collectors.groupingBy(Metric::getThreadName));
// int maxUsers = metricsMap.size();
// int sumElapsedTime = metricList.stream().mapToInt(metric -> Integer.parseInt(metric.getElapsed())).sum();
// String timeStamp = "";
// try {
// timeStamp = formatDate(entry.getKey());
// } catch (ParseException e) {
// e.printStackTrace();
// }
//
// ChartsData chartsData = new ChartsData();
// chartsData.setxAxis(timeStamp);
// chartsData.setGroupName("users");
// chartsData.setyAxis(new BigDecimal(maxUsers));
// chartsDataList.add(chartsData);
//
// ChartsData chartsData2 = new ChartsData();
// chartsData2.setxAxis(timeStamp);
// chartsData2.setGroupName("responseTime");
// chartsData2.setyAxis(new BigDecimal(sumElapsedTime * 1.0 / metricList.size()));
// chartsDataList.add(chartsData2);
//
// }
//
// return chartsDataList;
// }
public
static
List
<
ChartsData
>
getResponseTimeChartData
(
String
jtlString
)
{
Map
<
String
,
Object
>
activeThreadMap
=
getResultDataMap
(
jtlString
,
new
ActiveThreadsGraphConsumer
());
Map
<
String
,
Object
>
responseTimeMap
=
getResultDataMap
(
jtlString
,
new
ResponseTimeOverTimeGraphConsumer
());
...
...
@@ -463,40 +366,52 @@ public class JtlResolver {
}
public
static
void
mapResolver
(
Map
<
String
,
Object
>
map
,
List
list
,
String
seriesName
)
{
// ThreadGroup-1
for
(
String
key
:
map
.
keySet
())
{
MapResultData
mapResultData
=
(
MapResultData
)
map
.
get
(
key
);
ResultData
maxY
=
mapResultData
.
getResult
(
"maxY"
);
ListResultData
series
=
(
ListResultData
)
mapResultData
.
getResult
(
"series"
);
if
(
series
.
getSize
()
>
0
)
{
MapResultData
resultData
=
(
MapResultData
)
series
.
get
(
0
);
ListResultData
data
=
(
ListResultData
)
resultData
.
getResult
(
"data"
);
if
(
data
.
getSize
()
>
0
)
{
for
(
int
i
=
0
;
i
<
data
.
getSize
();
i
++)
{
ListResultData
listResultData
=
(
ListResultData
)
data
.
get
(
i
);
String
result
=
listResultData
.
accept
(
new
JsonizerVisitor
());
result
=
result
.
substring
(
1
,
result
.
length
()
-
1
);
String
[]
split
=
result
.
split
(
","
);
ChartsData
chartsData
=
new
ChartsData
();
BigDecimal
bigDecimal
=
new
BigDecimal
(
split
[
0
]);
String
timeStamp
=
bigDecimal
.
toPlainString
();
String
time
=
null
;
try
{
time
=
formatDate
(
stampToDate
(
DATE_TIME_PATTERN
,
timeStamp
));
}
catch
(
ParseException
e
)
{
e
.
printStackTrace
();
for
(
int
j
=
0
;
j
<
series
.
getSize
();
j
++)
{
MapResultData
resultData
=
(
MapResultData
)
series
.
get
(
j
);
// data, isOverall, label, isController
ListResultData
data
=
(
ListResultData
)
resultData
.
getResult
(
"data"
);
ValueResultData
label
=
(
ValueResultData
)
resultData
.
getResult
(
"label"
);
if
(
data
.
getSize
()
>
0
)
{
for
(
int
i
=
0
;
i
<
data
.
getSize
();
i
++)
{
ListResultData
listResultData
=
(
ListResultData
)
data
.
get
(
i
);
String
result
=
listResultData
.
accept
(
new
JsonizerVisitor
());
result
=
result
.
substring
(
1
,
result
.
length
()
-
1
);
String
[]
split
=
result
.
split
(
","
);
ChartsData
chartsData
=
new
ChartsData
();
BigDecimal
bigDecimal
=
new
BigDecimal
(
split
[
0
]);
String
timeStamp
=
bigDecimal
.
toPlainString
();
String
time
=
null
;
try
{
time
=
formatDate
(
stampToDate
(
DATE_TIME_PATTERN
,
timeStamp
));
}
catch
(
ParseException
e
)
{
e
.
printStackTrace
();
}
chartsData
.
setxAxis
(
time
);
chartsData
.
setyAxis
(
new
BigDecimal
(
split
[
1
].
trim
()));
if
(
series
.
getSize
()
==
1
)
{
chartsData
.
setGroupName
(
seriesName
);
}
else
{
chartsData
.
setGroupName
((
String
)
label
.
getValue
());
}
list
.
add
(
chartsData
);
}
chartsData
.
setxAxis
(
time
);
chartsData
.
setyAxis
(
new
BigDecimal
(
split
[
1
].
trim
()));
chartsData
.
setGroupName
(
seriesName
);
list
.
add
(
chartsData
);
}
}
}
}
}
public
static
Map
<
String
,
Object
>
getResultDataMap
(
String
jtlString
,
AbstractOverTimeGraphConsumer
timeGraphConsumer
)
{
int
row
=
0
;
AbstractOverTimeGraphConsumer
abstractOverTimeGraphConsumer
=
timeGraphConsumer
;
abstractOverTimeGraphConsumer
.
setGranularity
(
60000
);
// 使用反射获取properties
...
...
@@ -512,7 +427,7 @@ public class JtlResolver {
while
(
tokenizer
.
hasMoreTokens
())
{
String
line
=
tokenizer
.
nextToken
();
String
[]
data
=
line
.
split
(
","
,
-
1
);
Sample
sample
=
new
Sample
(
0
,
sampleMetaData
,
data
);
Sample
sample
=
new
Sample
(
row
++
,
sampleMetaData
,
data
);
abstractOverTimeGraphConsumer
.
consume
(
sample
,
0
);
}
abstractOverTimeGraphConsumer
.
stopConsuming
();
...
...
backend/src/main/java/io/metersphere/service/TestCaseService.java
浏览文件 @
22be52a5
...
...
@@ -224,7 +224,7 @@ public class TestCaseService {
}
private
List
<
TestCaseExcelData
>
generateExportTemplate
()
{
List
<
TestCaseExcelData
>
list
=
new
ArrayList
<
TestCaseExcelData
>();
List
<
TestCaseExcelData
>
list
=
new
ArrayList
<>();
StringBuilder
path
=
new
StringBuilder
(
""
);
List
<
String
>
types
=
Arrays
.
asList
(
"functional"
,
"performance"
,
"api"
);
List
<
String
>
methods
=
Arrays
.
asList
(
"manual"
,
"auto"
);
...
...
backend/src/main/java/io/metersphere/service/TestPlanTestCaseService.java
浏览文件 @
22be52a5
package
io.metersphere.service
;
import
io.metersphere.base.domain.TestPlanTestCase
;
import
io.metersphere.base.domain.TestPlanTestCaseExample
;
import
io.metersphere.base.mapper.TestPlanTestCaseMapper
;
import
io.metersphere.base.mapper.ext.ExtTestCaseMapper
;
import
io.metersphere.commons.utils.BeanUtils
;
import
io.metersphere.controller.request.testcase.QueryTestCaseRequest
;
import
io.metersphere.controller.request.testcase.TestCaseBatchRequest
;
import
io.metersphere.controller.request.testplancase.QueryTestPlanCaseRequest
;
import
io.metersphere.dto.TestPlanCaseDTO
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
...
...
@@ -21,11 +26,10 @@ public class TestPlanTestCaseService {
@Resource
ExtTestCaseMapper
extTestCaseMapper
;
public
List
<
TestPlanCaseDTO
>
getTestPlanCases
(
QueryTestCaseRequest
request
)
{
public
List
<
TestPlanCaseDTO
>
getTestPlanCases
(
QueryTest
Plan
CaseRequest
request
)
{
return
extTestCaseMapper
.
getTestPlanTestCases
(
request
);
}
public
void
editTestCase
(
TestPlanTestCase
testPlanTestCase
)
{
testPlanTestCase
.
setUpdateTime
(
System
.
currentTimeMillis
());
testPlanTestCaseMapper
.
updateByPrimaryKeySelective
(
testPlanTestCase
);
...
...
@@ -34,4 +38,15 @@ public class TestPlanTestCaseService {
public
int
deleteTestCase
(
Integer
id
)
{
return
testPlanTestCaseMapper
.
deleteByPrimaryKey
(
id
);
}
public
void
editTestCaseBath
(
TestCaseBatchRequest
request
)
{
TestPlanTestCaseExample
testPlanTestCaseExample
=
new
TestPlanTestCaseExample
();
testPlanTestCaseExample
.
createCriteria
().
andIdIn
(
request
.
getIds
());
TestPlanTestCase
testPlanTestCase
=
new
TestPlanTestCase
();
BeanUtils
.
copyBean
(
testPlanTestCase
,
request
);
testPlanTestCaseMapper
.
updateByExampleSelective
(
testPlanTestCase
,
testPlanTestCaseExample
);
}
}
backend/src/main/resources/i18n/messages_zh_CN.properties
浏览文件 @
22be52a5
error_lang_invalid
=
语言参数错误
load_test_already_exists
=
测试名称不能重复
project_name_is_null
=
项目名称不能为空
project_name_already_exists
=
项目名称已存在
workspace_name_is_null
=
工作空间名不能为空
workspace_name_already_exists
=
工作空间名已存在
workspace_does_not_belong_to_user
=
当前工作空间不属于当前用户
organization_does_not_belong_to_user
=
当前组织不属于当前用户
file_cannot_be_null
=
文件不能为空!
edit_load_test_not_found
=
无法编辑测试,未找到测试:
run_load_test_not_found
=
无法运行测试,未找到测试:
run_load_test_file_not_found
=
无法运行测试,无法获取测试文件元信息,测试ID:
run_load_test_file_content_not_found
=
无法运行测试,无法获取测试文件内容,测试ID:
run_load_test_file_init_error
=
无法运行测试,初始化运行环境失败,测试ID:
load_test_is_running
=
测试正在运行, 请等待
node_deep_limit
=
节点深度不超过5层!
no_nodes_message
=
没有节点信息
duplicate_node_ip
=
节点 IP 重复
only_one_k8s
=
只能添加一个
K8s
organization_id_is_null
=
组织 ID 不能为空
max_thread_insufficient
=
并发用户数超额
error_lang_invalid
=
\u
8BED
\u
8A00
\u
53C2
\u6570\u9519\u
8BEF
load_test_already_exists
=
\u
6D4B
\u
8BD5
\u
540D
\u
79F0
\u
4E0D
\u
80FD
\u
91CD
\u
590D
project_name_is_null
=
\u9879\u
76EE
\u
540D
\u
79F0
\u
4E0D
\u
80FD
\u
4E3A
\u
7A7A
project_name_already_exists
=
\u9879\u
76EE
\u
540D
\u
79F0
\u
5DF2
\u
5B58
\u5728
workspace_name_is_null
=
\u
5DE5
\u
4F5C
\u
7A7A
\u
95F4
\u
540D
\u
4E0D
\u
80FD
\u
4E3A
\u
7A7A
workspace_name_already_exists
=
\u
5DE5
\u
4F5C
\u
7A7A
\u
95F4
\u
540D
\u
5DF2
\u
5B58
\u5728
workspace_does_not_belong_to_user
=
\u
5F53
\u
524D
\u
5DE5
\u
4F5C
\u
7A7A
\u
95F4
\u
4E0D
\u
5C5E
\u
4E8E
\u
5F53
\u
524D
\u7528\u6237
organization_does_not_belong_to_user
=
\u
5F53
\u
524D
\u
7EC4
\u
7EC7
\u
4E0D
\u
5C5E
\u
4E8E
\u
5F53
\u
524D
\u7528\u6237
file_cannot_be_null
=
\u6587\u
4EF6
\u
4E0D
\u
80FD
\u
4E3A
\u
7A7A
\u
FF01
edit_load_test_not_found
=
\u
65E0
\u
6CD5
\u
7F16
\u
8F91
\u
6D4B
\u
8BD5
\u
FF0C
\u
672A
\u
627E
\u5230\u
6D4B
\u
8BD5
\u
FF1A
run_load_test_not_found
=
\u
65E0
\u
6CD5
\u
8FD0
\u
884C
\u
6D4B
\u
8BD5
\u
FF0C
\u
672A
\u
627E
\u5230\u
6D4B
\u
8BD5
\u
FF1A
run_load_test_file_not_found
=
\u
65E0
\u
6CD5
\u
8FD0
\u
884C
\u
6D4B
\u
8BD5
\u
FF0C
\u
65E0
\u
6CD5
\u
83B7
\u
53D6
\u
6D4B
\u
8BD5
\u6587\u
4EF6
\u5143\u
4FE1
\u
606F
\u
FF0C
\u
6D4B
\u
8BD5ID
\u
FF1A
run_load_test_file_content_not_found
=
\u
65E0
\u
6CD5
\u
8FD0
\u
884C
\u
6D4B
\u
8BD5
\u
FF0C
\u
65E0
\u
6CD5
\u
83B7
\u
53D6
\u
6D4B
\u
8BD5
\u6587\u
4EF6
\u5185\u
5BB9
\u
FF0C
\u
6D4B
\u
8BD5ID
\u
FF1A
run_load_test_file_init_error
=
\u
65E0
\u
6CD5
\u
8FD0
\u
884C
\u
6D4B
\u
8BD5
\u
FF0C
\u
521D
\u
59CB
\u5316\u
8FD0
\u
884C
\u
73AF
\u5883\u5931\u
8D25
\u
FF0C
\u
6D4B
\u
8BD5ID
\u
FF1A
load_test_is_running
=
\u
6D4B
\u
8BD5
\u
6B63
\u5728\u
8FD0
\u
884C,
\u
8BF7
\u
7B49
\u
5F85
node_deep_limit
=
\u8282\u
70B9
\u
6DF1
\u
5EA6
\u
4E0D
\u
8D85
\u
8FC75
\u
5C42
\u
FF01
no_nodes_message
=
\u
6CA1
\u6709\u8282\u
70B9
\u
4FE1
\u
606F
duplicate_node_ip
=
\u8282\u
70B9 IP
\u
91CD
\u
590D
only_one_k8s
=
\u
53EA
\u
80FD
\u
6DFB
\u
52A0
\u
4E00
\u
4E2A
K8s
organization_id_is_null
=
\u
7EC4
\u
7EC7 ID
\u
4E0D
\u
80FD
\u
4E3A
\u
7A7A
max_thread_insufficient
=
\u
5E76
\u
53D1
\u7528\u6237\u6570\u
8D85
\u
989D
\ No newline at end of file
frontend/src/business/components/api/test/ApiScenarioConfig.vue
浏览文件 @
22be52a5
...
...
@@ -26,13 +26,14 @@
</ms-api-collapse-item>
</ms-api-collapse>
</div>
<el-button
class=
"scenario-create"
type=
"primary"
size=
"mini"
icon=
"el-icon-plus"
plain
@
click=
"create"
/>
<el-button
class=
"scenario-create"
type=
"primary"
size=
"mini"
icon=
"el-icon-plus"
plain
@
click=
"createScenario"
/>
</el-aside>
<el-main
class=
"scenario-main"
>
<div
class=
"scenario-form"
>
<ms-api-scenario-form
:scenario=
"selected"
></ms-api-scenario-form>
<ms-api-request-form
:request=
"selected"
></ms-api-request-form>
<ms-api-scenario-form
:scenario=
"selected"
v-if=
"isScenario"
></ms-api-scenario-form>
<ms-api-request-form
:request=
"selected"
v-if=
"isRequest"
></ms-api-request-form>
</div>
</el-main>
</el-container>
...
...
@@ -49,6 +50,7 @@
import
MsApiRequest
from
"
./components/ApiRequest
"
;
import
MsApiRequestForm
from
"
./components/ApiRequestForm
"
;
import
MsApiScenarioForm
from
"
./components/ApiScenarioForm
"
;
import
{
Scenario
,
Request
}
from
"
./model/APIModel
"
;
export
default
{
name
:
"
MsApiScenarioConfig
"
,
...
...
@@ -64,6 +66,17 @@
},
methods
:
{
createScenario
:
function
()
{
let
scenario
=
new
Scenario
({
name
:
"
Scenario
"
});
this
.
scenarios
.
push
(
scenario
);
},
deleteScenario
:
function
(
index
)
{
this
.
scenarios
.
splice
(
index
,
1
);
if
(
this
.
scenarios
.
length
===
0
)
{
this
.
createScenario
();
this
.
select
(
this
.
scenarios
[
0
]);
}
},
handleChange
:
function
(
index
)
{
this
.
select
(
this
.
scenarios
[
index
]);
},
...
...
@@ -74,35 +87,23 @@
break
;
}
},
createScenario
:
function
()
{
return
{
type
:
"
Scenario
"
,
name
:
"
Scenario
"
,
address
:
""
,
file
:
""
,
variables
:
[],
headers
:
[],
requests
:
[]
}
},
deleteScenario
:
function
(
index
)
{
this
.
scenarios
.
splice
(
index
,
1
);
if
(
this
.
scenarios
.
length
===
0
)
{
this
.
create
();
}
},
create
:
function
()
{
let
scenario
=
this
.
createScenario
();
this
.
scenarios
.
push
(
scenario
);
},
select
:
function
(
obj
)
{
this
.
selected
=
obj
;
}
},
computed
:
{
isScenario
()
{
return
this
.
selected
instanceof
Scenario
;
},
isRequest
()
{
return
this
.
selected
instanceof
Request
;
}
},
created
()
{
if
(
this
.
scenarios
.
length
===
0
)
{
this
.
create
();
this
.
create
Scenario
();
this
.
select
(
this
.
scenarios
[
0
]);
}
}
...
...
@@ -137,6 +138,9 @@
}
.scenario-name
{
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
font-size
:
14px
;
width
:
100%
;
}
...
...
frontend/src/business/components/api/test/components/ApiAssertionRegex.vue
0 → 100644
浏览文件 @
22be52a5
<
template
>
<div>
<el-row
:gutter=
"10"
type=
"flex"
align=
"middle"
>
<el-col
:span=
"4"
>
<el-select
class=
"assertion-item"
v-model=
"regex.subject"
size=
"small"
:placeholder=
"$t('api_test.request.assertions.select_subject')"
>
<el-option
label=
"Http-Code"
value=
"HTTP-CODE"
></el-option>
<el-option
label=
"Header"
value=
"HEADER"
></el-option>
<el-option
label=
"Body"
value=
"BODY"
></el-option>
</el-select>
</el-col>
<el-col
:span=
"19"
>
<el-input
v-model=
"regex.expression"
maxlength=
"255"
size=
"small"
show-word-limit
:placeholder=
"$t('api_test.request.assertions.expression')"
/>
</el-col>
<el-col
:span=
"1"
class=
"assertion-btn"
>
<el-button
type=
"danger"
size=
"mini"
icon=
"el-icon-delete"
circle
@
click=
"remove"
v-if=
"edit"
/>
<el-button
type=
"primary"
size=
"small"
icon=
"el-icon-plus"
plain
@
click=
"add"
v-else
/>
</el-col>
</el-row>
</div>
</
template
>
<
script
>
import
{
Regex
}
from
"
../model/APIModel
"
;
export
default
{
name
:
"
MsApiAssertionRegex
"
,
props
:
{
regex
:
{
type
:
Regex
,
default
:
()
=>
{
return
new
Regex
();
}
},
edit
:
{
type
:
Boolean
,
default
:
false
},
index
:
Number
,
list
:
Array
},
methods
:
{
add
:
function
()
{
this
.
list
.
push
(
new
Regex
(
this
.
regex
));
},
remove
:
function
()
{
this
.
list
.
splice
(
this
.
index
,
1
);
}
}
}
</
script
>
<
style
scoped
>
.assertion-item
{
width
:
100%
;
}
.assertion-btn
{
text-align
:
center
;
}
</
style
>
frontend/src/business/components/api/test/components/ApiAssertionResponseTime.vue
0 → 100644
浏览文件 @
22be52a5
<
template
>
<div>
<el-row
:gutter=
"10"
align=
"middle"
>
<el-col
:span=
"23"
>
<el-input
v-model=
"time"
step=
"100"
size=
"small"
type=
"number"
:placeholder=
"$t('api_test.request.assertions.response_in_time')"
/>
</el-col>
<el-col
:span=
"1"
class=
"assertion-btn"
>
<el-button
type=
"danger"
size=
"mini"
icon=
"el-icon-delete"
circle
@
click=
"remove"
v-if=
"edit"
/>
<el-button
type=
"primary"
size=
"small"
icon=
"el-icon-plus"
plain
@
click=
"add"
v-else
/>
</el-col>
</el-row>
</div>
</
template
>
<
script
>
import
{
ResponseTime
}
from
"
../model/APIModel
"
;
export
default
{
name
:
"
MsApiAssertionResponseTime
"
,
props
:
{
edit
:
Boolean
,
responseTime
:
ResponseTime
},
data
()
{
return
{
time
:
this
.
responseTime
.
responseInTime
}
},
methods
:
{
add
:
function
()
{
this
.
remove
();
setTimeout
(()
=>
{
this
.
responseTime
.
responseInTime
=
this
.
time
;
})
},
remove
:
function
()
{
this
.
responseTime
.
responseInTime
=
null
;
}
}
}
</
script
>
<
style
scoped
>
.assertion-btn
{
text-align
:
center
;
}
</
style
>
frontend/src/business/components/api/test/components/ApiAssertionText.vue
0 → 100644
浏览文件 @
22be52a5
<
template
>
<div>
<el-row
:gutter=
"10"
>
<el-col
:span=
"4"
>
<el-select
class=
"assertion-item"
v-model=
"subject"
size=
"small"
:placeholder=
"$t('api_test.request.assertions.select_subject')"
>
<el-option
label=
"Http-Code"
value=
"HTTP-CODE"
></el-option>
<el-option
label=
"Header"
value=
"HEADER"
></el-option>
<el-option
label=
"Body"
value=
"BODY"
></el-option>
</el-select>
</el-col>
<el-col
:span=
"4"
>
<el-select
class=
"assertion-item"
v-model=
"condition"
size=
"small"
:placeholder=
"$t('api_test.request.assertions.select_contains')"
>
<el-option
:label=
"$t('api_test.request.assertions.contains')"
value=
"CONTAINS"
></el-option>
<el-option
:label=
"$t('api_test.request.assertions.not_contains')"
value=
"NOT_CONTAINS"
></el-option>
<el-option
:label=
"$t('api_test.request.assertions.equals')"
value=
"EQUALS"
></el-option>
<el-option
:label=
"$t('api_test.request.assertions.start_with')"
value=
"START_WITH"
></el-option>
<el-option
:label=
"$t('api_test.request.assertions.end_with')"
value=
"END_WITH"
></el-option>
</el-select>
</el-col>
<el-col
:span=
"15"
>
<el-input
v-model=
"value"
maxlength=
"255"
size=
"small"
show-word-limit
:placeholder=
"$t('api_test.request.assertions.value')"
/>
</el-col>
<el-col
:span=
"1"
>
<el-button
type=
"primary"
size=
"small"
icon=
"el-icon-plus"
plain
@
click=
"add"
/>
</el-col>
</el-row>
</div>
</
template
>
<
script
>
import
{
Regex
}
from
"
../model/APIModel
"
;
export
default
{
name
:
"
MsApiAssertionText
"
,
props
:
{
list
:
Array
},
data
()
{
return
{
subject
:
""
,
condition
:
""
,
value
:
""
}
},
methods
:
{
add
:
function
()
{
this
.
list
.
push
(
this
.
toRegex
());
},
toRegex
:
function
()
{
let
expression
=
""
;
let
description
=
""
;
switch
(
this
.
condition
)
{
case
"
CONTAINS
"
:
expression
=
"
.*
"
+
this
.
value
+
"
.*
"
;
description
=
"
contains:
"
+
this
.
value
;
break
;
case
"
NOT_CONTAINS
"
:
expression
=
"
^((?!
"
+
this
.
value
+
"
).)*$
"
;
description
=
"
not contains:
"
+
this
.
value
;
break
;
case
"
EQUALS
"
:
expression
=
"
^
"
+
this
.
value
+
"
$
"
;
description
=
"
equals:
"
+
this
.
value
;
break
;
case
"
START_WITH
"
:
expression
=
"
^
"
+
this
.
value
;
description
=
"
start with:
"
+
this
.
value
;
break
;
case
"
END_WITH
"
:
expression
=
this
.
value
+
"
$
"
;
description
=
"
end with:
"
+
this
.
value
;
break
;
}
return
new
Regex
({
subject
:
this
.
subject
,
expression
:
expression
,
description
:
description
}
);
}
}
}
</
script
>
<
style
scoped
>
.assertion-item
{
width
:
100%
;
}
</
style
>
frontend/src/business/components/api/test/components/ApiAssertions.vue
0 → 100644
浏览文件 @
22be52a5
<
template
>
<div>
<el-row
:gutter=
"10"
>
<el-col
:span=
"4"
>
<el-select
class=
"assertion-item"
v-model=
"type"
:placeholder=
"$t('api_test.request.assertions.select_type')"
size=
"small"
>
<el-option
:label=
"$t('api_test.request.assertions.text')"
:value=
"options.TEXT"
/>
<el-option
:label=
"$t('api_test.request.assertions.regex')"
:value=
"options.REGEX"
/>
<el-option
:label=
"$t('api_test.request.assertions.response_time')"
:value=
"options.RESPONSE_TIME"
/>
</el-select>
</el-col>
<el-col
:span=
"20"
>
<ms-api-assertion-text
:list=
"assertions.regex"
v-if=
"type === options.TEXT"
/>
<ms-api-assertion-regex
:list=
"assertions.regex"
v-if=
"type === options.REGEX"
/>
<ms-api-assertion-response-time
:response-time=
"assertions.responseTime"
v-if=
"type === options.RESPONSE_TIME"
/>
</el-col>
</el-row>
<ms-api-assertions-edit
:assertions=
"assertions"
/>
</div>
</
template
>
<
script
>
import
MsApiAssertionText
from
"
./ApiAssertionText
"
;
import
MsApiAssertionRegex
from
"
./ApiAssertionRegex
"
;
import
MsApiAssertionResponseTime
from
"
./ApiAssertionResponseTime
"
;
import
{
ASSERTION_TYPE
,
Assertions
,
Regex
}
from
"
../model/APIModel
"
;
import
MsApiAssertionsEdit
from
"
./ApiAssertionsEdit
"
;
export
default
{
name
:
"
MsApiAssertions
"
,
components
:
{
MsApiAssertionsEdit
,
MsApiAssertionResponseTime
,
MsApiAssertionRegex
,
MsApiAssertionText
},
props
:
{
assertions
:
Assertions
},
data
()
{
return
{
options
:
ASSERTION_TYPE
,
type
:
""
,
}
},
methods
:
{
createRegex
:
function
()
{
return
new
Regex
();
}
}
}
</
script
>
<
style
scoped
>
.assertion-item
{
width
:
100%
;
}
</
style
>
frontend/src/business/components/api/test/components/ApiAssertionsEdit.vue
0 → 100644
浏览文件 @
22be52a5
<
template
>
<div>
<div
class=
"assertion-item-editing regex"
v-if=
"assertions.regex.length > 0"
>
<div>
{{
$t
(
"
api_test.request.assertions.regex
"
)
}}
</div>
<div
class=
"regex-item"
v-for=
"(regex, index) in assertions.regex"
:key=
"index"
>
<ms-api-assertion-regex
:list=
"assertions.regex"
:regex=
"regex"
:edit=
"true"
:index=
"index"
/>
</div>
</div>
<div
class=
"assertion-item-editing response-time"
v-if=
"assertions.responseTime.isValid()"
>
<div>
{{
$t
(
"
api_test.request.assertions.response_time
"
)
}}
</div>
<ms-api-assertion-response-time
:response-time=
"assertions.responseTime"
:edit=
"true"
/>
</div>
</div>
</
template
>
<
script
>
import
MsApiAssertionRegex
from
"
./ApiAssertionRegex
"
;
import
MsApiAssertionResponseTime
from
"
./ApiAssertionResponseTime
"
;
import
{
Assertions
}
from
"
../model/APIModel
"
;
export
default
{
name
:
"
MsApiAssertionsEdit
"
,
components
:
{
MsApiAssertionResponseTime
,
MsApiAssertionRegex
},
props
:
{
assertions
:
Assertions
}
}
</
script
>
<
style
scoped
>
.assertion-item-editing
{
padding-left
:
10px
;
margin-top
:
10px
;
}
.assertion-item-editing.regex
{
border-left
:
2px
solid
#7B0274
;
}
.assertion-item-editing.response-time
{
border-left
:
2px
solid
#DD0240
;
}
.regex-item
{
margin-top
:
10px
;
}
</
style
>
frontend/src/business/components/api/test/components/ApiBody.vue
浏览文件 @
22be52a5
<
template
>
<div>
<el-radio-group
v-model=
"body.type"
size=
"mini"
>
<el-radio-button
label=
"kv
"
>
<el-radio-button
:label=
"type.KV
"
>
{{
$t
(
'
api_test.request.body_kv
'
)
}}
</el-radio-button>
<el-radio-button
label=
"text
"
>
<el-radio-button
:label=
"type.TEXT
"
>
{{
$t
(
'
api_test.request.body_text
'
)
}}
</el-radio-button>
</el-radio-group>
<ms-api-key-value
:items=
"body.kvs"
v-if=
"
isKV
"
/>
<ms-api-key-value
:items=
"body.kvs"
v-if=
"
body.isKV()
"
/>
<el-input
class=
"textarea"
type=
"textarea"
v-model=
"body.text"
:autosize=
"
{ minRows: 10, maxRows: 25}" resize="none"
v-else/>
...
...
@@ -18,23 +18,26 @@
<
script
>
import
MsApiKeyValue
from
"
./ApiKeyValue
"
;
import
{
Body
,
BODY_TYPE
}
from
"
../model/APIModel
"
;
export
default
{
name
:
"
MsApiBody
"
,
components
:
{
MsApiKeyValue
},
props
:
{
body
:
Object
body
:
Body
},
data
()
{
return
{};
return
{
type
:
BODY_TYPE
};
},
methods
:
{},
c
omputed
:
{
i
sKV
(
)
{
return
this
.
body
.
type
===
"
kv
"
;
c
reated
()
{
i
f
(
this
.
body
.
type
===
null
)
{
this
.
body
.
type
=
BODY_TYPE
.
KV
;
}
}
}
...
...
frontend/src/business/components/api/test/components/ApiKeyValue.vue
浏览文件 @
22be52a5
...
...
@@ -12,7 +12,8 @@
<el-input
v-model=
"item.value"
placeholder=
"Value"
size=
"small"
maxlength=
"100"
@
change=
"check"
/>
</el-col>
<el-col
:span=
"1"
>
<el-button
size=
"mini"
class=
"el-icon-delete-solid"
circle
@
click=
"remove(index)"
/>
<el-button
size=
"mini"
class=
"el-icon-delete-solid"
circle
@
click=
"remove(index)"
:disabled=
"isDisable(index)"
/>
</el-col>
</el-row>
</div>
...
...
@@ -20,6 +21,8 @@
</
template
>
<
script
>
import
{
KeyValue
}
from
"
../model/APIModel
"
;
export
default
{
name
:
"
MsApiKeyValue
"
,
...
...
@@ -29,23 +32,20 @@
},
methods
:
{
create
:
function
()
{
return
{
key
:
""
,
value
:
""
}
add
:
function
()
{
this
.
items
.
push
(
new
KeyValue
());
},
remove
:
function
(
index
)
{
this
.
items
.
splice
(
index
,
1
);
if
(
this
.
items
.
length
===
0
)
{
this
.
items
.
push
(
this
.
create
()
);
this
.
add
(
);
}
},
check
:
function
()
{
let
isNeedCreate
=
true
;
let
removeIndex
=
-
1
;
this
.
items
.
forEach
((
item
,
index
)
=>
{
if
(
item
.
key
===
""
&&
item
.
value
===
""
)
{
if
(
!
item
.
key
&&
!
item
.
value
)
{
// 多余的空行
if
(
index
!==
this
.
items
.
length
-
1
)
{
removeIndex
=
index
;
...
...
@@ -55,18 +55,21 @@
}
});
if
(
isNeedCreate
)
{
this
.
items
.
push
(
this
.
create
()
);
this
.
add
(
);
}
if
(
removeIndex
!==
-
1
)
{
this
.
remove
(
removeIndex
);
}
// TODO 检查key重复
},
isDisable
:
function
(
index
)
{
return
this
.
items
.
length
-
1
===
index
;
}
},
created
()
{
if
(
this
.
items
.
length
===
0
)
{
this
.
items
.
push
(
this
.
create
()
);
this
.
add
(
);
}
}
}
...
...
frontend/src/business/components/api/test/components/ApiRequest.vue
浏览文件 @
22be52a5
...
...
@@ -2,28 +2,30 @@
<div
class=
"request-container"
>
<div
class=
"request-item"
v-for=
"(request, index) in requests"
:key=
"index"
@
click=
"select(request)"
:class=
"
{'selected': isSelected(request)}">
<span
class=
"request-method"
>
{{
request
.
method
}}
</span>
<span
class=
"request-name"
>
{{
request
.
name
}}
</span>
<span
class=
"request-btn"
>
<el-dropdown
trigger=
"click"
@
command=
"handleCommand"
>
<span
class=
"el-dropdown-link el-icon-more"
></span>
<el-dropdown-menu
slot=
"dropdown"
>
<el-dropdown-item
:command=
"
{type: 'copy', index: index}">复制请求
</el-dropdown-item>
<el-dropdown-item
:command=
"
{type: 'delete', index: index}">删除请求
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</span>
<el-row
type=
"flex"
>
<div
class=
"request-method"
>
{{
request
.
method
}}
</div>
<div
class=
"request-name"
>
{{
request
.
name
}}
</div>
<div
class=
"request-btn"
>
<el-dropdown
trigger=
"click"
@
command=
"handleCommand"
>
<span
class=
"el-dropdown-link el-icon-more"
></span>
<el-dropdown-menu
slot=
"dropdown"
>
<el-dropdown-item
:command=
"
{type: 'copy', index: index}">复制请求
</el-dropdown-item>
<el-dropdown-item
:command=
"
{type: 'delete', index: index}">删除请求
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</el-row>
</div>
<el-button
class=
"request-create"
type=
"primary"
size=
"mini"
icon=
"el-icon-plus"
plain
@
click=
"create"
/>
<el-button
class=
"request-create"
type=
"primary"
size=
"mini"
icon=
"el-icon-plus"
plain
@
click=
"create
Request
"
/>
</div>
</
template
>
<
script
>
import
{
generateId
}
from
'
element-ui/src/utils/util
'
;
import
{
Request
}
from
"
../model/APIModel
"
;
export
default
{
name
:
"
MsApiRequest
"
,
...
...
@@ -34,7 +36,7 @@
data
()
{
return
{
selected
:
0
selected
:
0
,
}
},
...
...
@@ -47,20 +49,10 @@
},
methods
:
{
create
:
function
()
{
let
request
=
this
.
createRequest
(
);
create
Request
:
function
()
{
let
request
=
new
Request
({
method
:
"
GET
"
}
);
this
.
requests
.
push
(
request
);
},
handleCommand
:
function
(
command
)
{
switch
(
command
.
type
)
{
case
"
copy
"
:
this
.
copyRequest
(
command
.
index
);
break
;
case
"
delete
"
:
this
.
deleteRequest
(
command
.
index
);
break
;
}
},
copyRequest
:
function
(
index
)
{
let
request
=
this
.
requests
[
index
];
this
.
requests
.
push
(
JSON
.
parse
(
JSON
.
stringify
(
request
)));
...
...
@@ -68,24 +60,17 @@
deleteRequest
:
function
(
index
)
{
this
.
requests
.
splice
(
index
,
1
);
if
(
this
.
requests
.
length
===
0
)
{
this
.
create
();
this
.
create
Request
();
}
},
createRequest
:
function
()
{
return
{
randomId
:
generateId
(),
type
:
"
Request
"
,
method
:
"
GET
"
,
name
:
""
,
parameters
:
[],
headers
:
[],
body
:
{
type
:
"
kv
"
,
kvs
:
[],
text
:
""
},
assertions
:
[],
extract
:
[]
handleCommand
:
function
(
command
)
{
switch
(
command
.
type
)
{
case
"
copy
"
:
this
.
copyRequest
(
command
.
index
);
break
;
case
"
delete
"
:
this
.
deleteRequest
(
command
.
index
);
break
;
}
},
select
:
function
(
request
)
{
...
...
@@ -96,7 +81,7 @@
created
()
{
if
(
this
.
requests
.
length
===
0
)
{
this
.
create
();
this
.
create
Request
();
this
.
select
(
this
.
requests
[
0
]);
}
}
...
...
@@ -126,11 +111,13 @@
.request-method
{
padding
:
0
5px
;
width
:
60px
;
color
:
#1E90FF
;
}
.request-name
{
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
font-size
:
14px
;
width
:
100%
;
}
...
...
frontend/src/business/components/api/test/components/ApiRequestForm.vue
浏览文件 @
22be52a5
<
template
>
<el-form
:model=
"request"
:rules=
"rules"
ref=
"request"
label-width=
"100px"
label-position=
"left"
v-if=
"isRequest"
>
<el-form
:model=
"request"
:rules=
"rules"
ref=
"request"
label-width=
"100px"
>
<el-form-item
:label=
"$t('api_test.request.name')"
prop=
"name"
>
<el-input
v-model=
"request.name"
></el-input>
<el-input
v-model=
"request.name"
maxlength=
"100"
></el-input>
</el-form-item>
<el-form-item
:label=
"$t('api_test.request.url')"
prop=
"url"
>
<el-input
v-model=
"request.url"
:placeholder=
"$t('api_test.request.url_describe
')"
>
<el-input
v-model=
"request.url"
maxlength=
"100"
:placeholder=
"$t('api_test.request.url_description
')"
>
<el-select
v-model=
"request.method"
slot=
"prepend"
class=
"request-method-select"
>
<el-option
label=
"GET"
value=
"GET"
></el-option>
<el-option
label=
"POST"
value=
"POST"
></el-option>
...
...
@@ -29,8 +29,8 @@
<el-tab-pane
:label=
"$t('api_test.request.body')"
name=
"body"
v-if=
"isNotGet"
>
<ms-api-body
:body=
"request.body"
/>
</el-tab-pane>
<el-tab-pane
:label=
"$t('api_test.request.assertions
')"
name=
"assertions"
v-if=
"false
"
>
TODO
<el-tab-pane
:label=
"$t('api_test.request.assertions
.label')"
name=
"assertions
"
>
<ms-api-assertions
:assertions=
"request.assertions"
></ms-api-assertions>
</el-tab-pane>
<el-tab-pane
:label=
"$t('api_test.request.extract')"
name=
"extract"
v-if=
"false"
>
TODO
...
...
@@ -42,10 +42,11 @@
<
script
>
import
MsApiKeyValue
from
"
./ApiKeyValue
"
;
import
MsApiBody
from
"
./ApiBody
"
;
import
MsApiAssertions
from
"
./ApiAssertions
"
;
export
default
{
name
:
"
MsApiRequestForm
"
,
components
:
{
MsApiBody
,
MsApiKeyValue
},
components
:
{
MsApi
Assertions
,
MsApi
Body
,
MsApiKeyValue
},
props
:
{
request
:
Object
},
...
...
@@ -53,14 +54,19 @@
data
()
{
return
{
activeName
:
"
parameters
"
,
rules
:
{}
rules
:
{
name
:
[
{
required
:
true
,
message
:
this
.
$t
(
'
api_test.scenario.input_name
'
),
trigger
:
'
blur
'
},
{
max
:
100
,
message
:
this
.
$t
(
'
commons.input_limit
'
,
[
0
,
100
]),
trigger
:
'
blur
'
}
],
url
:
[
{
max
:
100
,
message
:
this
.
$t
(
'
commons.input_limit
'
,
[
0
,
100
]),
trigger
:
'
blur
'
}
]
}
}
},
computed
:
{
isRequest
()
{
return
this
.
request
.
type
===
"
Request
"
;
},
isNotGet
()
{
return
this
.
request
.
method
!==
"
GET
"
;
}
...
...
frontend/src/business/components/api/test/components/ApiScenarioForm.vue
浏览文件 @
22be52a5
<
template
>
<el-form
:model=
"scenario"
:rules=
"rules"
ref=
"scenario"
label-width=
"100px"
label-position=
"left"
v-if=
"isScenario"
>
<el-form
:model=
"scenario"
:rules=
"rules"
ref=
"scenario"
label-width=
"100px"
>
<el-form-item
:label=
"$t('api_test.scenario.name')"
prop=
"name"
>
<el-input
v-model=
"scenario.name"
></el-input>
<el-input
v-model=
"scenario.name"
maxlength=
"100"
></el-input>
</el-form-item>
<el-form-item
:label=
"$t('api_test.scenario.base_url')"
prop=
"url"
>
<el-input
:placeholder=
"$t('api_test.scenario.base_url_descri
be')"
v-model=
"scenario.url"
></el-input
>
<el-input
:placeholder=
"$t('api_test.scenario.base_url_descri
ption')"
v-model=
"scenario.url"
maxlength=
"100"
/
>
</el-form-item>
<el-tabs
v-model=
"activeName"
>
...
...
@@ -32,15 +32,17 @@
data
()
{
return
{
activeName
:
"
variables
"
,
rules
:
{}
rules
:
{
name
:
[
{
required
:
true
,
message
:
this
.
$t
(
'
api_test.scenario.input_name
'
),
trigger
:
'
blur
'
},
{
max
:
100
,
message
:
this
.
$t
(
'
commons.input_limit
'
,
[
0
,
100
]),
trigger
:
'
blur
'
}
],
url
:
[
{
max
:
100
,
message
:
this
.
$t
(
'
commons.input_limit
'
,
[
0
,
100
]),
trigger
:
'
blur
'
}
]
}
}
},
computed
:
{
isScenario
()
{
return
this
.
scenario
.
type
===
"
Scenario
"
;
}
},
}
}
</
script
>
...
...
frontend/src/business/components/api/test/model/APIModel.js
0 → 100644
浏览文件 @
22be52a5
import
{
generateId
}
from
"
element-ui/src/utils/util
"
;
const
assign
=
function
(
obj
,
options
)
{
for
(
let
name
in
options
)
{
if
(
options
.
hasOwnProperty
(
name
))
{
obj
[
name
]
=
options
[
name
];
}
}
}
export
const
BODY_TYPE
=
{
KV
:
"
KV
"
,
TEXT
:
"
TEXT
"
}
export
const
ASSERTION_TYPE
=
{
TEXT
:
"
TEXT
"
,
REGEX
:
"
REGEX
"
,
RESPONSE_TIME
:
"
RESPONSE_TIME
"
}
export
class
Scenario
{
constructor
(
options
)
{
this
.
name
=
null
;
this
.
url
=
null
;
this
.
variables
=
[];
this
.
headers
=
[];
this
.
requests
=
[];
assign
(
this
,
options
);
}
}
export
class
Request
{
constructor
(
options
)
{
this
.
randomId
=
generateId
();
this
.
name
=
null
;
this
.
url
=
null
;
this
.
method
=
null
;
this
.
parameters
=
[];
this
.
headers
=
[];
this
.
body
=
new
Body
();
this
.
assertions
=
new
Assertions
();
this
.
extract
=
[];
assign
(
this
,
options
);
}
}
export
class
Body
{
constructor
(
options
)
{
this
.
type
=
null
;
this
.
text
=
null
;
this
.
kvs
=
[];
assign
(
this
,
options
);
}
isKV
()
{
return
this
.
type
===
BODY_TYPE
.
KV
;
}
}
export
class
KeyValue
{
constructor
(
options
)
{
this
.
key
=
null
;
this
.
value
=
null
;
assign
(
this
,
options
);
}
}
export
class
Assertions
{
constructor
(
options
)
{
this
.
text
=
[];
this
.
regex
=
[];
this
.
responseTime
=
new
ResponseTime
();
assign
(
this
,
options
);
}
}
class
AssertionType
{
constructor
(
type
)
{
this
.
type
=
type
;
}
}
export
class
Text
extends
AssertionType
{
constructor
(
options
)
{
super
(
ASSERTION_TYPE
.
TEXT
);
this
.
subject
=
null
;
this
.
condition
=
null
;
this
.
value
=
null
;
assign
(
this
,
options
);
}
}
export
class
Regex
extends
AssertionType
{
constructor
(
options
)
{
super
(
ASSERTION_TYPE
.
REGEX
);
this
.
subject
=
null
;
this
.
expression
=
null
;
this
.
description
=
null
;
assign
(
this
,
options
);
}
}
export
class
ResponseTime
extends
AssertionType
{
constructor
(
options
)
{
super
(
ASSERTION_TYPE
.
RESPONSE_TIME
);
this
.
responseInTime
=
null
;
assign
(
this
,
options
);
}
isValid
()
{
return
this
.
responseInTime
!==
null
&&
this
.
responseInTime
>
0
;
}
}
frontend/src/business/components/common/components/MsDialogFooter.vue
0 → 100644
浏览文件 @
22be52a5
<
template
>
<div
class=
"dialog-footer"
>
<el-button
@
click=
"cancel"
>
取 消
</el-button>
<el-button
type=
"primary"
@
click=
"confirm"
>
确 定
</el-button>
</div>
</
template
>
<
script
>
export
default
{
name
:
"
MsDialogFooter
"
,
methods
:
{
cancel
()
{
this
.
$emit
(
"
cancel
"
);
},
confirm
()
{
this
.
$emit
(
"
confirm
"
);
}
}
}
</
script
>
<
style
scoped
>
</
style
>
frontend/src/business/components/common/components/MsTipButton.vue
0 → 100644
浏览文件 @
22be52a5
<
template
>
<el-tooltip
:disabled=
"disabled"
:content=
"tip"
placement=
"bottom"
:effect=
"effect"
>
<el-button
@
click=
"exec()"
circle
:type=
"type"
:icon=
"icon"
:size=
"size"
/>
</el-tooltip>
</
template
>
<
script
>
export
default
{
name
:
"
MsTipButton
"
,
props
:
{
tip
:
String
,
icon
:
{
type
:
String
,
default
:
'
el-icon-question
'
},
type
:
{
type
:
String
,
default
:
null
},
effect
:
{
type
:
String
,
default
:
'
dark
'
},
size
:
{
type
:
String
,
default
:
'
mini
'
},
disabled
:
{
type
:
Boolean
,
default
:
false
}
},
methods
:
{
exec
()
{
this
.
$emit
(
'
click
'
);
}
}
}
</
script
>
<
style
scoped
>
</
style
>
frontend/src/business/components/performance/report/components/TestOverview.vue
浏览文件 @
22be52a5
...
...
@@ -157,7 +157,8 @@
this
.
$get
(
"
/performance/report/content/res_chart/
"
+
this
.
id
,
res
=>
{
let
data
=
res
.
data
;
let
userList
=
data
.
filter
(
m
=>
m
.
groupName
===
"
users
"
).
map
(
m
=>
m
.
yAxis
);
let
responseTimeList
=
data
.
filter
(
m
=>
m
.
groupName
===
"
responseTime
"
).
map
(
m
=>
m
.
yAxis
);
let
responseTimeList
=
data
.
filter
(
m
=>
m
.
groupName
!=
"
users
"
).
map
(
m
=>
m
.
yAxis
);
let
responseGroupNameList
=
data
.
filter
(
m
=>
m
.
groupName
!=
"
users
"
).
map
(
m
=>
m
.
groupName
);
let
userMax
=
this
.
_getChartMax
(
userList
);
let
resMax
=
this
.
_getChartMax
(
responseTimeList
);
let
resOption
=
{
...
...
@@ -171,7 +172,24 @@
},
tooltip
:
{
show
:
true
,
trigger
:
'
axis
'
trigger
:
'
axis
'
,
extraCssText
:
'
z-index: 999;
'
,
formatter
:
function
(
params
,
ticket
,
callback
)
{
let
result
=
""
;
let
name
=
params
[
0
].
name
;
result
+=
name
+
"
<br/>
"
;
for
(
let
i
=
0
;
i
<
params
.
length
;
i
++
)
{
let
seriesName
=
params
[
i
].
seriesName
;
if
(
seriesName
.
length
>
100
)
{
seriesName
=
seriesName
.
substring
(
0
,
100
);
}
let
value
=
params
[
i
].
value
;
let
marker
=
params
[
i
].
marker
;
result
+=
marker
+
seriesName
+
"
:
"
+
value
[
1
]
+
"
<br/>
"
;
}
return
result
;
}
},
legend
:
{},
xAxis
:
{},
...
...
@@ -197,14 +215,12 @@
{
name
:
'
users
'
,
color
:
'
#0CA74A
'
,
},
{
name
:
"
responseTime
"
,
yAxisIndex
:
'
1
'
,
color
:
'
#99743C
'
,
}
]
}
responseGroupNameList
.
forEach
(
item
=>
{
setting
[
"
series
"
].
splice
(
0
,
0
,
{
name
:
item
,
yAxisIndex
:
'
1
'
})
})
this
.
resOption
=
this
.
generateOption
(
resOption
,
data
,
setting
);
})
},
...
...
@@ -229,10 +245,11 @@
legend
.
push
(
name
)
series
[
name
]
=
[]
}
series
[
name
].
splice
(
xAxis
.
indexOf
(
item
.
xAxis
),
0
,
[
item
.
xAxis
,
Math
.
round
(
item
.
yAxis
.
toFixed
(
2
)
)]);
series
[
name
].
splice
(
xAxis
.
indexOf
(
item
.
xAxis
),
0
,
[
item
.
xAxis
,
item
.
yAxis
.
toFixed
(
2
)]);
})
this
.
$set
(
option
.
legend
,
"
data
"
,
legend
);
this
.
$set
(
option
.
legend
,
"
bottom
"
,
10
);
this
.
$set
(
option
.
legend
,
"
type
"
,
"
scroll
"
);
this
.
$set
(
option
.
legend
,
"
bottom
"
,
"
10px
"
);
this
.
$set
(
option
.
xAxis
,
"
data
"
,
xAxis
);
for
(
let
name
in
series
)
{
let
d
=
series
[
name
];
...
...
@@ -259,9 +276,6 @@
_getChartMax
(
arr
)
{
const
max
=
Math
.
max
(...
arr
);
return
Math
.
ceil
(
max
/
4.5
)
*
5
;
},
_arraySort
(
a
,
b
)
{
return
a
[
0
]
>
b
[
0
];
}
},
watch
:
{
...
...
frontend/src/business/components/track/case/components/TestCaseList.vue
浏览文件 @
22be52a5
...
...
@@ -89,23 +89,8 @@
</el-table-column>
</el-table>
<div>
<el-row>
<el-col
:span=
"22"
:offset=
"1"
>
<div
class=
"table-page"
>
<el-pagination
@
size-change=
"handleSizeChange"
@
current-change=
"handleCurrentChange"
:current-page.sync=
"currentPage"
:page-sizes=
"[5, 10, 20, 50, 100]"
:page-size=
"pageSize"
layout=
"total, sizes, prev, pager, next, jumper"
:total=
"total"
>
</el-pagination>
</div>
</el-col>
</el-row>
</div>
<ms-table-pagination
:change=
"initTableData"
:current-page.sync=
"currentPage"
:page-size.sync=
"pageSize"
:total=
"total"
/>
</el-card>
</el-main>
...
...
@@ -116,22 +101,21 @@
import
MsCreateBox
from
'
../../../settings/CreateBox
'
;
import
TestCaseImport
from
'
../components/TestCaseImport
'
;
import
TestCaseExport
from
'
../components/TestCaseExport
'
;
import
MsTablePagination
from
'
../../../../components/common/pagination/TablePagination
'
;
export
default
{
name
:
"
TestCaseList
"
,
components
:
{
MsCreateBox
,
TestCaseImport
,
TestCaseExport
},
components
:
{
MsCreateBox
,
TestCaseImport
,
TestCaseExport
,
MsTablePagination
},
data
()
{
return
{
result
:
{},
deletePath
:
"
/test/case/delete
"
,
condition
:
""
,
tableData
:
[],
multipleSelection
:
[],
currentPage
:
1
,
pageSize
:
5
,
total
:
0
,
loadingRequire
:
{
project
:
true
,
testCase
:
true
},
testId
:
null
}
},
props
:
{
...
...
@@ -157,7 +141,6 @@
if
(
this
.
currentProject
)
{
param
.
projectId
=
this
.
currentProject
.
id
;
this
.
result
=
this
.
$post
(
this
.
buildPagePath
(
'
/test/case/list
'
),
param
,
response
=>
{
this
.
loadingRequire
.
testCase
=
false
;
let
data
=
response
.
data
;
this
.
total
=
data
.
itemCount
;
this
.
tableData
=
data
.
listObject
;
...
...
@@ -173,17 +156,6 @@
testCaseCreate
()
{
this
.
$emit
(
'
openTestCaseEditDialog
'
);
},
handleSizeChange
(
size
)
{
this
.
pageSize
=
size
;
this
.
initTableData
();
},
handleCurrentChange
(
current
)
{
this
.
currentPage
=
current
;
this
.
initTableData
();
},
handleSelectionChange
(
val
)
{
this
.
multipleSelection
=
val
;
},
handleEdit
(
testCase
)
{
this
.
$emit
(
'
testCaseEdit
'
,
testCase
);
},
...
...
frontend/src/business/components/track/plan/common/TestPlanTestCaseStatusButton.vue
0 → 100644
浏览文件 @
22be52a5
<
template
>
<el-row
type=
"flex"
justify=
"start"
:gutter=
"20"
>
<el-col>
<el-button
type=
"success"
round
:icon=
"status == 'Pass' ? 'el-icon-check' : ''"
@
click=
"setStatus('Pass')"
>
{{
$t
(
'
test_track.pass
'
)
}}
</el-button>
</el-col>
<el-col
>
<el-button
type=
"danger"
round
:icon=
"status == 'Failure' ? 'el-icon-check' : ''"
@
click=
"setStatus('Failure')"
>
{{
$t
(
'
test_track.failure
'
)
}}
</el-button>
</el-col>
<el-col
>
<el-button
type=
"warning"
round
:icon=
"status == 'Blocking' ? 'el-icon-check' : ''"
@
click=
"setStatus('Blocking')"
>
{{
$t
(
'
test_track.blocking
'
)
}}
</el-button>
</el-col>
<el-col
>
<el-button
type=
"info"
round
:icon=
"status == 'Skip' ? 'el-icon-check' : ''"
@
click=
"setStatus('Skip')"
>
{{
$t
(
'
test_track.skip
'
)
}}
</el-button>
</el-col>
</el-row>
</
template
>
<
script
>
export
default
{
name
:
"
TestPlanTestCaseStatusButton
"
,
data
()
{
return
{
}
},
props
:
{
status
:
{
type
:
String
}
},
methods
:
{
setStatus
(
status
)
{
this
.
$emit
(
'
statusChange
'
,
status
);
}
}
}
</
script
>
<
style
scoped
>
.el-row
{
width
:
50%
;
}
</
style
>
frontend/src/business/components/track/plan/components/ExecutorEdit.vue
0 → 100644
浏览文件 @
22be52a5
<
template
>
<el-dialog
title=
"更改执行人"
:visible.sync=
"executorEditVisible"
width=
"20%"
>
<el-select
v-model=
"executor"
placeholder=
"请选择活动区域"
>
<el-option
v-for=
"item in executorOptions"
:key=
"item.id"
:label=
"item.name"
:value=
"item.name"
></el-option>
</el-select>
<template
v-slot:footer
>
<ms-dialog-footer
@
cancel=
"executorEditVisible = false"
@
confirm=
"saveExecutor"
/>
</
template
>
</el-dialog>
</template>
<
script
>
import
{
WORKSPACE_ID
}
from
'
../../../../../common/js/constants
'
import
MsDialogFooter
from
'
../../../common/components/MsDialogFooter
'
export
default
{
name
:
"
executorEdit
"
,
components
:
{
MsDialogFooter
},
data
()
{
return
{
executorEditVisible
:
false
,
executor
:
''
,
executorOptions
:
[]
}
},
props
:
{
selectIds
:
{
type
:
Set
}
},
methods
:
{
setMaintainerOptions
()
{
let
workspaceId
=
localStorage
.
getItem
(
WORKSPACE_ID
);
this
.
$post
(
'
/user/ws/member/list/all
'
,
{
workspaceId
:
workspaceId
},
response
=>
{
this
.
executorOptions
=
response
.
data
;
});
},
saveExecutor
()
{
let
param
=
{};
param
.
executor
=
this
.
executor
;
if
(
this
.
executor
===
''
)
{
this
.
$message
(
'
请选择执行人!
'
);
return
;
}
param
.
ids
=
[...
this
.
selectIds
];
this
.
$post
(
'
/test/plan/case/batch/edit
'
,
param
,
()
=>
{
this
.
executor
=
''
;
this
.
selectIds
.
clear
();
this
.
$message
.
success
(
"
保存成功
"
);
this
.
executorEditVisible
=
false
;
this
.
$emit
(
'
refresh
'
);
});
},
openExecutorEdit
()
{
this
.
executorEditVisible
=
true
;
this
.
setMaintainerOptions
();
}
}
}
</
script
>
<
style
scoped
>
</
style
>
frontend/src/business/components/track/plan/components/StatusEdit.vue
0 → 100644
浏览文件 @
22be52a5
<
template
>
<el-dialog
title=
"更改执行结果"
:visible.sync=
"statusEditVisible"
width=
"30%"
>
<test-plan-test-case-status-button
@
statusChange=
"statusChange"
:status=
"status"
/>
<template
v-slot:footer
>
<ms-dialog-footer
@
cancel=
"statusEditVisible = false"
@
confirm=
"saveStatus"
/>
</
template
>
</el-dialog>
</template>
<
script
>
import
TestPlanTestCaseStatusButton
from
'
../common/TestPlanTestCaseStatusButton
'
;
import
MsDialogFooter
from
'
../../../common/components/MsDialogFooter
'
export
default
{
name
:
"
statusEdit
"
,
components
:
{
TestPlanTestCaseStatusButton
,
MsDialogFooter
},
data
()
{
return
{
statusEditVisible
:
false
,
status
:
''
}
},
props
:
{
selectIds
:
{
type
:
Set
}
},
methods
:
{
statusChange
(
status
)
{
this
.
status
=
status
;
},
saveStatus
()
{
let
param
=
{};
if
(
this
.
status
===
''
)
{
this
.
$message
(
'
请选择执行结果!
'
);
return
;
}
param
.
status
=
this
.
status
;
param
.
ids
=
[...
this
.
selectIds
];
this
.
$post
(
'
/test/plan/case/batch/edit
'
,
param
,
()
=>
{
this
.
selectIds
.
clear
();
this
.
status
=
''
;
this
.
$message
.
success
(
"
保存成功
"
);
this
.
statusEditVisible
=
false
;
this
.
$emit
(
'
refresh
'
);
});
},
openStatusEdit
()
{
this
.
statusEditVisible
=
true
;
}
}
}
</
script
>
<
style
scoped
>
</
style
>
frontend/src/business/components/track/plan/components/TestCaseRelevance.vue
浏览文件 @
22be52a5
...
...
@@ -143,7 +143,6 @@
}
},
close
()
{
console
.
log
(
"
clear
"
);
this
.
selectIds
.
clear
();
}
}
...
...
frontend/src/business/components/track/plan/components/TestPlanList.vue
浏览文件 @
22be52a5
...
...
@@ -16,7 +16,7 @@
<el-input
type=
"text"
size=
"small"
:placeholder=
"$t('load_test.search_by_name')"
prefix-icon=
"el-icon-search"
maxlength=
"60"
v-model=
"condition"
@
change=
"
search
"
clearable
/>
v-model=
"condition"
@
change=
"
initTableData
"
clearable
/>
</span>
</el-col>
</el-row>
...
...
@@ -88,23 +88,25 @@
</el-table-column>
</el-table>
<div>
<el-row>
<el-col
:span=
"22"
:offset=
"1"
>
<div
class=
"table-page"
>
<el-pagination
@
size-change=
"handleSizeChange"
@
current-change=
"handleCurrentChange"
:current-page.sync=
"currentPage"
:page-sizes=
"[5, 10, 20, 50, 100]"
:page-size=
"pageSize"
layout=
"total, sizes, prev, pager, next, jumper"
:total=
"total"
>
</el-pagination>
</div>
</el-col>
</el-row>
</div>
<!--<div>-->
<!--<el-row>-->
<!--<el-col :span="22" :offset="1">-->
<!--<div class="table-page">-->
<!--<el-pagination-->
<!--@size-change="handleSizeChange"-->
<!--@current-change="handleCurrentChange"-->
<!--:current-page.sync="currentPage"-->
<!--:page-sizes="[5, 10, 20, 50, 100]"-->
<!--:page-size="pageSize"-->
<!--layout="total, sizes, prev, pager, next, jumper"-->
<!--:total="total">-->
<!--</el-pagination>-->
<!--</div>-->
<!--</el-col>-->
<!--</el-row>-->
<!--</div>-->
<ms-table-pagination
:change=
"initTableData"
:current-page.sync=
"currentPage"
:page-size.sync=
"pageSize"
:total=
"total"
/>
</el-card>
</el-main>
...
...
@@ -113,22 +115,21 @@
<
script
>
import
MsCreateBox
from
'
../../../settings/CreateBox
'
;
import
MsTablePagination
from
'
../../../../components/common/pagination/TablePagination
'
;
export
default
{
name
:
"
TestPlanList
"
,
components
:
{
MsCreateBox
},
components
:
{
MsCreateBox
,
MsTablePagination
},
data
()
{
return
{
result
:
{},
queryPath
:
"
/test/plan/list
"
,
deletePath
:
"
/test/plan/delete
"
,
condition
:
""
,
tableData
:
[],
multipleSelection
:
[],
currentPage
:
1
,
pageSize
:
5
,
total
:
0
,
t
estId
:
null
,
t
ableData
:
[]
,
}
},
created
()
{
...
...
@@ -146,26 +147,12 @@
this
.
tableData
=
data
.
listObject
;
});
},
search
()
{
this
.
initTableData
();
},
buildPagePath
(
path
)
{
return
path
+
"
/
"
+
this
.
currentPage
+
"
/
"
+
this
.
pageSize
;
},
testPlanCreate
()
{
this
.
$emit
(
'
openTestPlanEditDialog
'
);
},
handleSizeChange
(
size
)
{
this
.
pageSize
=
size
;
this
.
initTableData
();
},
handleCurrentChange
(
current
)
{
this
.
currentPage
=
current
;
this
.
initTableData
();
},
handleSelectionChange
(
val
)
{
this
.
multipleSelection
=
val
;
},
handleEdit
(
testPlan
)
{
this
.
$emit
(
'
testPlanEdit
'
,
testPlan
);
},
...
...
frontend/src/business/components/track/plan/components/TestPlanTestCaseEdit.vue
浏览文件 @
22be52a5
...
...
@@ -82,7 +82,7 @@
</el-col>
</el-row>
<el-row
>
<el-row>
<el-col
:span=
"15"
:offset=
"1"
>
<div
style=
"margin-bottom: 5px;"
>
<span
class=
"cast_label"
>
{{$t('commons.remark')}}:
</span>
...
...
@@ -98,28 +98,9 @@
</el-col>
</el-row>
<el-row>
<el-col
:offset=
"1"
:span=
"2"
>
<el-button
type=
"success"
round
:icon=
"testCase.status == 'Pass' ? 'el-icon-check' : ''"
@
click=
"setTestCaseStatus('Pass')"
>
{{$t('test_track.pass')}}
</el-button>
</el-col>
<el-col
:span=
"2"
>
<el-button
type=
"danger"
round
:icon=
"testCase.status == 'Failure' ? 'el-icon-check' : ''"
@
click=
"setTestCaseStatus('Failure')"
>
{{$t('test_track.failure')}}
</el-button>
</el-col>
<el-col
:span=
"2"
>
<el-button
type=
"warning"
round
:icon=
"testCase.status == 'Blocking' ? 'el-icon-check' : ''"
@
click=
"setTestCaseStatus('Blocking')"
>
{{$t('test_track.blocking')}}
</el-button>
</el-col>
<el-col
:span=
"2"
>
<el-button
type=
"info"
round
:icon=
"testCase.status == 'Skip' ? 'el-icon-check' : ''"
@
click=
"setTestCaseStatus('Skip')"
>
{{$t('test_track.skip')}}
</el-button>
</el-col>
</el-row>
<test-plan-test-case-status-button
class=
"status-button"
@
statusChange=
"statusChange"
:status=
"testCase.status"
/>
<el-row
type=
"flex"
justify=
"end"
>
<el-col
:span=
"5"
>
...
...
@@ -136,12 +117,15 @@
</template>
<
script
>
export
default
{
import
TestPlanTestCaseStatusButton
from
'
../common/TestPlanTestCaseStatusButton
'
;
export
default
{
name
:
"
TestPlanTestCaseEdit
"
,
data
()
{
components
:
{
TestPlanTestCaseStatusButton
},
data
()
{
return
{
dialog
:
false
,
testCase
:
{}
testCase
:
{
TestPlanTestCaseStatusButton
}
};
},
methods
:
{
...
...
@@ -151,7 +135,7 @@
cancel
()
{
this
.
dialog
=
false
;
},
s
etTestCaseStatus
(
status
)
{
s
tatusChange
(
status
)
{
this
.
testCase
.
status
=
status
;
},
saveCase
()
{
...
...
@@ -197,4 +181,8 @@
color
:
dimgray
;
}
.status-button
{
padding-left
:
4%
;
}
</
style
>
frontend/src/business/components/track/plan/components/TestPlanTestCaseList.vue
浏览文件 @
22be52a5
...
...
@@ -4,22 +4,47 @@
<el-card
v-loading=
"result.loading"
>
<template
v-slot:header
>
<div>
<el-row
type=
"flex"
justify=
"space-between"
align=
"middle"
>
<el-col
:span=
"5"
>
<span
class=
"title"
>
{{
$t
(
'
test_track.test_case
'
)
}}
</span>
<el-row
type=
"flex"
justify=
"end"
>
<el-col>
<span
class=
"title"
>
{{
$t
(
'
test_track.test_case
'
)
}}
</span>
<ms-tip-button
v-if=
"!showMyTestCase"
:tip=
"'我的用例'"
icon=
"el-icon-s-custom"
@
click=
"searchMyTestCase"
/>
<ms-tip-button
v-if=
"showMyTestCase"
:tip=
"'全部用例'"
icon=
"el-icon-files"
@
click=
"searchMyTestCase"
/>
</el-col>
<el-col
:
span=
"2"
:
offset=
"8"
>
<el-col
:offset=
"8"
>
<el-button
icon=
"el-icon-connection"
size=
"small"
round
@
click=
"$emit('openTestCaseRelevanceDialog')"
>
{{
$t
(
'
test_track.relevance_test_case
'
)
}}
</el-button>
</el-col>
<el-col
:span=
"5"
>
<el-col>
<el-button
icon=
"el-icon-edit-outline"
size=
"small"
round
@
click=
"handleBatch('status')"
>
更改执行结果
</el-button>
</el-col>
<el-col>
<el-button
icon=
"el-icon-user"
size=
"small"
round
@
click=
"handleBatch('executor')"
>
更改执行人
</el-button>
</el-col>
<executor-edit
ref=
"executorEdit"
:select-ids=
"selectIds"
@
refresh=
"initTableData"
/>
<status-edit
ref=
"statusEdit"
:select-ids=
"selectIds"
@
refresh=
"initTableData"
/>
<el-col>
<span
class=
"search"
>
<el-input
type=
"text"
size=
"small"
:placeholder=
"$t('load_test.search_by_name')"
prefix-icon=
"el-icon-search"
maxlength=
"60"
v-model=
"condition"
@
change=
"search"
clearable
/>
v-model=
"condition
.name
"
@
change=
"search"
clearable
/>
</span>
</el-col>
</el-row>
...
...
@@ -27,8 +52,14 @@
</
template
>
<el-table
@
select-all=
"handleSelectAll"
@
select=
"handleSelectionChange"
row-key=
"id"
:data=
"tableData"
>
<el-table-column
type=
"selection"
></el-table-column>
<el-table-column
prop=
"name"
:label=
"$t('commons.name')"
>
...
...
@@ -106,23 +137,8 @@
</el-table-column>
</el-table>
<div>
<el-row>
<el-col
:span=
"22"
:offset=
"1"
>
<div
class=
"table-page"
>
<el-pagination
@
size-change=
"handleSizeChange"
@
current-change=
"handleCurrentChange"
:current-page.sync=
"currentPage"
:page-sizes=
"[5, 10, 20, 50, 100]"
:page-size=
"pageSize"
layout=
"total, sizes, prev, pager, next, jumper"
:total=
"total"
>
</el-pagination>
</div>
</el-col>
</el-row>
</div>
<ms-table-pagination
:change=
"search"
:current-page.sync=
"currentPage"
:page-size.sync=
"pageSize"
:total=
"total"
/>
</el-card>
</el-main>
...
...
@@ -130,22 +146,26 @@
<
script
>
import
PlanNodeTree
from
'
./PlanNodeTree
'
;
import
ExecutorEdit
from
'
./ExecutorEdit
'
;
import
StatusEdit
from
'
./StatusEdit
'
;
import
MsTipButton
from
'
../../../../components/common/components/MsTipButton
'
;
import
MsTablePagination
from
'
../../../../components/common/pagination/TablePagination
'
;
import
{
TokenKey
}
from
'
../../../../../common/js/constants
'
;
export
default
{
name
:
"
TestPlanTestCaseList
"
,
components
:
{
PlanNodeTree
},
components
:
{
PlanNodeTree
,
StatusEdit
,
ExecutorEdit
,
MsTipButton
,
MsTablePagination
},
data
()
{
return
{
result
:
{},
deletePath
:
"
/test/case/delete
"
,
condition
:
""
,
condition
:
{},
showMyTestCase
:
false
,
tableData
:
[],
multipleSelection
:
[],
currentPage
:
1
,
pageSize
:
5
,
total
:
0
,
loadingRequire
:
{
project
:
true
,
testCase
:
true
},
testId
:
null
selectIds
:
new
Set
(),
}
},
props
:{
...
...
@@ -164,13 +184,11 @@
methods
:
{
initTableData
(
nodeIds
)
{
if
(
this
.
planId
)
{
let
param
=
{
name
:
this
.
condition
,
};
let
param
=
{};
Object
.
assign
(
param
,
this
.
condition
);
param
.
nodeIds
=
nodeIds
;
param
.
planId
=
this
.
planId
;
this
.
result
=
this
.
$post
(
this
.
buildPagePath
(
'
/test/plan/case/list
'
),
param
,
response
=>
{
this
.
loadingRequire
.
testCase
=
false
;
let
data
=
response
.
data
;
this
.
total
=
data
.
itemCount
;
this
.
tableData
=
data
.
listObject
;
...
...
@@ -183,17 +201,6 @@
buildPagePath
(
path
)
{
return
path
+
"
/
"
+
this
.
currentPage
+
"
/
"
+
this
.
pageSize
;
},
handleSizeChange
(
size
)
{
this
.
pageSize
=
size
;
this
.
initTableData
();
},
handleCurrentChange
(
current
)
{
this
.
currentPage
=
current
;
this
.
initTableData
();
},
handleSelectionChange
(
val
)
{
this
.
multipleSelection
=
val
;
},
handleEdit
(
testCase
)
{
this
.
$emit
(
'
editTestPlanTestCase
'
,
testCase
);
},
...
...
@@ -217,6 +224,43 @@
type
:
'
success
'
});
});
},
handleSelectAll
(
selection
)
{
if
(
selection
.
length
>
0
)
{
this
.
tableData
.
forEach
(
item
=>
{
this
.
selectIds
.
add
(
item
.
id
);
});
}
else
{
this
.
selectIds
.
clear
();
}
},
handleSelectionChange
(
selection
,
row
)
{
if
(
this
.
selectIds
.
has
(
row
.
id
)){
this
.
selectIds
.
delete
(
row
.
id
);
}
else
{
this
.
selectIds
.
add
(
row
.
id
);
}
},
handleBatch
(
type
){
if
(
this
.
selectIds
.
size
<
1
)
{
this
.
$message
.
warning
(
'
请选择需要操作的数据
'
);
return
;
}
if
(
type
===
'
executor
'
){
this
.
$refs
.
executorEdit
.
openExecutorEdit
();
}
else
if
(
type
===
'
status
'
){
this
.
$refs
.
statusEdit
.
openStatusEdit
();
}
},
searchMyTestCase
()
{
this
.
showMyTestCase
=
!
this
.
showMyTestCase
;
if
(
this
.
showMyTestCase
)
{
let
user
=
JSON
.
parse
(
localStorage
.
getItem
(
TokenKey
));
this
.
condition
.
executor
=
user
.
id
;
}
else
{
this
.
condition
.
executor
=
null
;
}
this
.
initTableData
();
}
}
}
...
...
@@ -231,10 +275,4 @@
}
/*.main-content {*/
/*margin: 0 auto;*/
/*width: 100%;*/
/*max-width: 1200px;*/
/*}*/
</
style
>
frontend/src/i18n/zh-CN.js
浏览文件 @
22be52a5
...
...
@@ -45,7 +45,8 @@ export default {
'
refresh
'
:
'
刷新
'
,
'
remark
'
:
'
备注
'
,
'
delete
'
:
'
删除
'
,
'
not_filled
'
:
'
未填写
'
'
not_filled
'
:
'
未填写
'
,
'
please_select
'
:
'
请选择
'
,
},
workspace
:
{
'
create
'
:
'
创建工作空间
'
,
...
...
@@ -169,25 +170,43 @@ export default {
},
api_test
:
{
scenario
:
{
input_name
:
"
请输入场景名称
"
,
name
:
"
场景名称
"
,
base_url
:
"
基础URL
"
,
base_url_descri
be
:
"
基础URL作为所有请求的URL前缀
"
,
base_url_descri
ption
:
"
基础URL作为所有请求的URL前缀
"
,
variables
:
"
变量
"
,
headers
:
"
请求头
"
},
request
:
{
input_name
:
"
请输入请求名称
"
,
name
:
"
请求名称
"
,
method
:
"
请求方法
"
,
url
:
"
请求URL
"
,
url_descri
be
:
"
例如: https://fit2cloud.com
"
,
url_descri
ption
:
"
例如: https://fit2cloud.com
"
,
parameters
:
"
请求参数
"
,
parameters_desc
:
"
参数追加到URL,例如https://fit2cloud.com/entries?key1=Value1&Key2=Value2
"
,
headers
:
"
请求头
"
,
body
:
"
请求内容
"
,
body_kv
:
"
键值对
"
,
body_text
:
"
文本
"
,
assertions
:
"
断言
"
,
extract
:
"
提取
"
assertions
:
{
label
:
"
断言
"
,
text
:
"
文本
"
,
regex
:
"
正则
"
,
response_time
:
"
响应时间
"
,
select_type
:
"
请选择类型
"
,
select_subject
:
"
请选择对象
"
,
select_contains
:
"
请选择条件
"
,
contains
:
"
包含
"
,
not_contains
:
"
不包含
"
,
equals
:
"
等于
"
,
start_with
:
"
以...开始
"
,
end_with
:
"
以...结束
"
,
value
:
"
值
"
,
expression
:
"
正则表达式
"
,
response_in_time
:
"
响应时间在...毫秒以内
"
,
},
extract
:
"
提取
"
,
}
},
test_track
:
{
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录