Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
MeterSphere
metersphere
提交
b2d0e0cf
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,发现更多精彩内容 >>
提交
b2d0e0cf
编写于
4月 22, 2020
作者:
Q
q4speed
浏览文件
操作
浏览文件
下载
差异文件
Merge remote-tracking branch 'origin/dev' into dev
上级
6aa69cee
be78fc59
变更
37
展开全部
隐藏空白更改
内联
并排
Showing
37 changed file
with
1284 addition
and
1348 deletion
+1284
-1348
backend/src/main/java/io/metersphere/controller/PerformanceReportController.java
...o/metersphere/controller/PerformanceReportController.java
+3
-8
backend/src/main/java/io/metersphere/controller/PerformanceTestController.java
.../io/metersphere/controller/PerformanceTestController.java
+6
-0
backend/src/main/java/io/metersphere/controller/TestPlanTestCaseController.java
...io/metersphere/controller/TestPlanTestCaseController.java
+5
-0
backend/src/main/java/io/metersphere/engine/docker/DockerTestEngine.java
...n/java/io/metersphere/engine/docker/DockerTestEngine.java
+2
-5
backend/src/main/java/io/metersphere/engine/kubernetes/KubernetesTestEngine.java
...o/metersphere/engine/kubernetes/KubernetesTestEngine.java
+4
-2
backend/src/main/java/io/metersphere/report/GenerateReport.java
...d/src/main/java/io/metersphere/report/GenerateReport.java
+41
-305
backend/src/main/java/io/metersphere/report/base/ErrorsTop5.java
.../src/main/java/io/metersphere/report/base/ErrorsTop5.java
+85
-13
backend/src/main/java/io/metersphere/report/base/Statistics.java
.../src/main/java/io/metersphere/report/base/Statistics.java
+134
-0
backend/src/main/java/io/metersphere/report/base/SummaryData.java
...src/main/java/io/metersphere/report/base/SummaryData.java
+16
-0
backend/src/main/java/io/metersphere/report/dto/ErrorsTop5DTO.java
...rc/main/java/io/metersphere/report/dto/ErrorsTop5DTO.java
+0
-135
backend/src/main/java/io/metersphere/report/dto/RequestStatisticsDTO.java
.../java/io/metersphere/report/dto/RequestStatisticsDTO.java
+0
-128
backend/src/main/java/io/metersphere/report/parse/ResultDataParse.java
...ain/java/io/metersphere/report/parse/ResultDataParse.java
+55
-4
backend/src/main/java/io/metersphere/service/PerformanceTestService.java
...n/java/io/metersphere/service/PerformanceTestService.java
+19
-0
backend/src/main/java/io/metersphere/service/ReportService.java
...d/src/main/java/io/metersphere/service/ReportService.java
+6
-14
backend/src/main/resources/i18n/messages_en_US.properties
backend/src/main/resources/i18n/messages_en_US.properties
+3
-1
backend/src/main/resources/i18n/messages_zh_CN.properties
backend/src/main/resources/i18n/messages_zh_CN.properties
+3
-1
backend/src/test/java/io/metersphere/JtlTest.java
backend/src/test/java/io/metersphere/JtlTest.java
+0
-69
backend/src/test/java/io/metersphere/ResultDataParseTest.java
...end/src/test/java/io/metersphere/ResultDataParseTest.java
+36
-0
frontend/src/business/components/common/components/MsTableSearchBar.vue
...usiness/components/common/components/MsTableSearchBar.vue
+34
-0
frontend/src/business/components/common/components/MsTipButton.vue
...src/business/components/common/components/MsTipButton.vue
+0
-2
frontend/src/business/components/performance/report/PerformanceReportView.vue
...s/components/performance/report/PerformanceReportView.vue
+49
-19
frontend/src/business/components/performance/report/components/ErrorLog.vue
...ess/components/performance/report/components/ErrorLog.vue
+15
-47
frontend/src/business/components/performance/report/components/RequestStatistics.vue
...nents/performance/report/components/RequestStatistics.vue
+32
-46
frontend/src/business/components/track/case/TestCase.vue
frontend/src/business/components/track/case/TestCase.vue
+40
-53
frontend/src/business/components/track/case/components/TestCaseEdit.vue
...usiness/components/track/case/components/TestCaseEdit.vue
+151
-121
frontend/src/business/components/track/case/components/TestCaseImport.vue
...iness/components/track/case/components/TestCaseImport.vue
+2
-1
frontend/src/business/components/track/case/components/TestCaseList.vue
...usiness/components/track/case/components/TestCaseList.vue
+25
-20
frontend/src/business/components/track/common/NodeBreadcrumb.vue
...d/src/business/components/track/common/NodeBreadcrumb.vue
+50
-0
frontend/src/business/components/track/common/NodeEdit.vue
frontend/src/business/components/track/common/NodeEdit.vue
+101
-0
frontend/src/business/components/track/common/NodeTree.vue
frontend/src/business/components/track/common/NodeTree.vue
+47
-109
frontend/src/business/components/track/common/PlanNodeTree.vue
...end/src/business/components/track/common/PlanNodeTree.vue
+11
-2
frontend/src/business/components/track/plan/TestPlanView.vue
frontend/src/business/components/track/plan/TestPlanView.vue
+39
-27
frontend/src/business/components/track/plan/components/TestCaseRelevance.vue
...ss/components/track/plan/components/TestCaseRelevance.vue
+41
-31
frontend/src/business/components/track/plan/components/TestPlanTestCaseEdit.vue
...components/track/plan/components/TestPlanTestCaseEdit.vue
+186
-151
frontend/src/business/components/track/plan/components/TestPlanTestCaseList.vue
...components/track/plan/components/TestPlanTestCaseList.vue
+40
-33
frontend/src/i18n/en-US.js
frontend/src/i18n/en-US.js
+2
-1
frontend/src/i18n/zh-CN.js
frontend/src/i18n/zh-CN.js
+1
-0
未找到文件。
backend/src/main/java/io/metersphere/controller/PerformanceReportController.java
浏览文件 @
b2d0e0cf
...
...
@@ -8,12 +8,7 @@ import io.metersphere.commons.utils.PageUtils;
import
io.metersphere.commons.utils.Pager
;
import
io.metersphere.controller.request.ReportRequest
;
import
io.metersphere.dto.ReportDTO
;
import
io.metersphere.report.base.ChartsData
;
import
io.metersphere.report.base.Errors
;
import
io.metersphere.report.base.ReportTimeInfo
;
import
io.metersphere.report.base.TestOverview
;
import
io.metersphere.report.dto.ErrorsTop5DTO
;
import
io.metersphere.report.dto.RequestStatisticsDTO
;
import
io.metersphere.report.base.*
;
import
io.metersphere.service.ReportService
;
import
io.metersphere.user.SessionUtils
;
import
org.apache.shiro.authz.annotation.Logical
;
...
...
@@ -59,7 +54,7 @@ public class PerformanceReportController {
}
@GetMapping
(
"/content/{reportId}"
)
public
RequestStatisticsDTO
getReportContent
(
@PathVariable
String
reportId
)
{
public
List
<
Statistics
>
getReportContent
(
@PathVariable
String
reportId
)
{
return
reportService
.
getReport
(
reportId
);
}
...
...
@@ -69,7 +64,7 @@ public class PerformanceReportController {
}
@GetMapping
(
"/content/errors_top5/{reportId}"
)
public
ErrorsTop5DTO
getReportErrorsTop5
(
@PathVariable
String
reportId
)
{
public
List
<
ErrorsTop5
>
getReportErrorsTop5
(
@PathVariable
String
reportId
)
{
return
reportService
.
getReportErrorsTOP5
(
reportId
);
}
...
...
backend/src/main/java/io/metersphere/controller/PerformanceTestController.java
浏览文件 @
b2d0e0cf
...
...
@@ -21,6 +21,7 @@ import org.springframework.web.multipart.MultipartFile;
import
javax.annotation.Resource
;
import
java.util.List
;
import
java.util.Map
;
@RestController
@RequestMapping
(
value
=
"performance"
)
...
...
@@ -88,6 +89,11 @@ public class PerformanceTestController {
performanceTestService
.
run
(
request
);
}
@GetMapping
(
"/log/{testId}"
)
public
Map
<
String
,
String
>
stop
(
@PathVariable
String
testId
)
{
return
performanceTestService
.
log
(
testId
);
}
@GetMapping
(
"/file/metadata/{testId}"
)
public
List
<
FileMetadata
>
getFileMetadata
(
@PathVariable
String
testId
)
{
return
fileService
.
getFileMetadataByTestId
(
testId
);
...
...
backend/src/main/java/io/metersphere/controller/TestPlanTestCaseController.java
浏览文件 @
b2d0e0cf
...
...
@@ -29,6 +29,11 @@ public class TestPlanTestCaseController {
return
PageUtils
.
setPageInfo
(
page
,
testPlanTestCaseService
.
getTestPlanCases
(
request
));
}
@PostMapping
(
"/list/all"
)
public
List
<
TestPlanCaseDTO
>
getTestPlanCases
(
@RequestBody
QueryTestPlanCaseRequest
request
){
return
testPlanTestCaseService
.
getTestPlanCases
(
request
);
}
@PostMapping
(
"/edit"
)
public
void
editTestCase
(
@RequestBody
TestPlanTestCase
testPlanTestCase
){
testPlanTestCaseService
.
editTestCase
(
testPlanTestCase
);
...
...
backend/src/main/java/io/metersphere/engine/docker/DockerTestEngine.java
浏览文件 @
b2d0e0cf
...
...
@@ -10,7 +10,6 @@ import io.metersphere.dto.NodeDTO;
import
io.metersphere.engine.AbstractEngine
;
import
io.metersphere.engine.EngineContext
;
import
io.metersphere.engine.EngineFactory
;
import
io.metersphere.engine.docker.request.BaseRequest
;
import
io.metersphere.engine.docker.request.TestRequest
;
import
io.metersphere.i18n.Translator
;
import
org.springframework.web.client.RestTemplate
;
...
...
@@ -91,14 +90,13 @@ public class DockerTestEngine extends AbstractEngine {
public
void
stop
()
{
// TODO 停止运行测试
String
testId
=
loadTest
.
getId
();
BaseRequest
request
=
new
BaseRequest
();
this
.
resourceList
.
forEach
(
r
->
{
NodeDTO
node
=
JSON
.
parseObject
(
r
.
getConfiguration
(),
NodeDTO
.
class
);
String
ip
=
node
.
getIp
();
Integer
port
=
node
.
getPort
();
String
uri
=
String
.
format
(
BASE_URL
+
"/jmeter/container/stop/"
+
testId
,
ip
,
port
);
restTemplate
.
postForObject
(
uri
,
request
,
String
.
class
);
restTemplate
.
getForObject
(
uri
,
String
.
class
);
});
}
...
...
@@ -106,14 +104,13 @@ public class DockerTestEngine extends AbstractEngine {
public
Map
<
String
,
String
>
log
()
{
String
testId
=
loadTest
.
getId
();
Map
<
String
,
String
>
logs
=
new
HashMap
<>();
BaseRequest
request
=
new
BaseRequest
();
this
.
resourceList
.
forEach
(
r
->
{
NodeDTO
node
=
JSON
.
parseObject
(
r
.
getConfiguration
(),
NodeDTO
.
class
);
String
ip
=
node
.
getIp
();
Integer
port
=
node
.
getPort
();
String
uri
=
String
.
format
(
BASE_URL
+
"/jmeter/container/log/"
+
testId
,
ip
,
port
);
String
log
=
restTemplate
.
postForObject
(
uri
,
request
,
String
.
class
);
String
log
=
restTemplate
.
getForObject
(
uri
,
String
.
class
);
logs
.
put
(
node
.
getIp
(),
log
);
});
return
logs
;
...
...
backend/src/main/java/io/metersphere/engine/kubernetes/KubernetesTestEngine.java
浏览文件 @
b2d0e0cf
...
...
@@ -133,8 +133,10 @@ public class KubernetesTestEngine extends AbstractEngine {
ClientCredential
clientCredential
=
JSON
.
parseObject
(
configuration
,
ClientCredential
.
class
);
KubernetesProvider
provider
=
new
KubernetesProvider
(
JSON
.
toJSONString
(
clientCredential
));
provider
.
confirmNamespace
(
loadTest
.
getProjectId
());
String
joblog
=
provider
.
getKubernetesClient
().
batch
().
jobs
().
inNamespace
(
loadTest
.
getProjectId
()).
withName
(
"job-"
+
loadTest
.
getId
()).
getLog
();
logs
.
put
(
clientCredential
.
getMasterUrl
(),
joblog
);
try
(
KubernetesClient
client
=
provider
.
getKubernetesClient
())
{
String
joblog
=
client
.
batch
().
jobs
().
inNamespace
(
loadTest
.
getProjectId
()).
withName
(
"job-"
+
loadTest
.
getId
()).
getLog
();
logs
.
put
(
clientCredential
.
getMasterUrl
(),
joblog
);
}
}
catch
(
Exception
e
)
{
MSException
.
throwException
(
e
);
}
...
...
backend/src/main/java/io/metersphere/report/GenerateReport.java
浏览文件 @
b2d0e0cf
此差异已折叠。
点击以展开。
backend/src/main/java/io/metersphere/report/base/ErrorsTop5.java
浏览文件 @
b2d0e0cf
...
...
@@ -4,9 +4,17 @@ public class ErrorsTop5 {
private
String
sample
;
private
String
samples
;
private
Integer
errorsAllSize
;
private
String
error
;
private
String
errors
;
private
String
errorsAllSize
;
private
String
error1
;
private
String
error1Size
;
private
String
error2
;
private
String
error2Size
;
private
String
error3
;
private
String
error3Size
;
private
String
error4
;
private
String
error4Size
;
private
String
error5
;
private
String
error5Size
;
public
String
getSample
()
{
return
sample
;
...
...
@@ -24,27 +32,91 @@ public class ErrorsTop5 {
this
.
samples
=
samples
;
}
public
Integer
getErrorsAllSize
()
{
public
String
getErrorsAllSize
()
{
return
errorsAllSize
;
}
public
void
setErrorsAllSize
(
Integer
errorsAllSize
)
{
public
void
setErrorsAllSize
(
String
errorsAllSize
)
{
this
.
errorsAllSize
=
errorsAllSize
;
}
public
String
getError
()
{
return
error
;
public
String
getError
1
()
{
return
error
1
;
}
public
void
setError
(
String
error
)
{
this
.
error
=
error
;
public
void
setError
1
(
String
error1
)
{
this
.
error
1
=
error1
;
}
public
String
getError
s
()
{
return
error
s
;
public
String
getError
1Size
()
{
return
error
1Size
;
}
public
void
setErrors
(
String
errors
)
{
this
.
errors
=
errors
;
public
void
setError1Size
(
String
error1Size
)
{
this
.
error1Size
=
error1Size
;
}
public
String
getError2
()
{
return
error2
;
}
public
void
setError2
(
String
error2
)
{
this
.
error2
=
error2
;
}
public
String
getError2Size
()
{
return
error2Size
;
}
public
void
setError2Size
(
String
error2Size
)
{
this
.
error2Size
=
error2Size
;
}
public
String
getError3
()
{
return
error3
;
}
public
void
setError3
(
String
error3
)
{
this
.
error3
=
error3
;
}
public
String
getError3Size
()
{
return
error3Size
;
}
public
void
setError3Size
(
String
error3Size
)
{
this
.
error3Size
=
error3Size
;
}
public
String
getError4
()
{
return
error4
;
}
public
void
setError4
(
String
error4
)
{
this
.
error4
=
error4
;
}
public
String
getError4Size
()
{
return
error4Size
;
}
public
void
setError4Size
(
String
error4Size
)
{
this
.
error4Size
=
error4Size
;
}
public
String
getError5
()
{
return
error5
;
}
public
void
setError5
(
String
error5
)
{
this
.
error5
=
error5
;
}
public
String
getError5Size
()
{
return
error5Size
;
}
public
void
setError5Size
(
String
error5Size
)
{
this
.
error5Size
=
error5Size
;
}
}
backend/src/main/java/io/metersphere/report/base/
Request
Statistics.java
→
backend/src/main/java/io/metersphere/report/base/Statistics.java
浏览文件 @
b2d0e0cf
package
io.metersphere.report.base
;
public
class
Request
Statistics
{
public
class
Statistics
{
/**请求标签*/
private
String
requestLabel
;
private
String
label
;
/**压测请求数*/
private
Integer
samples
;
private
String
samples
;
private
String
ko
;
private
String
error
;
/**平均响应时间*/
private
String
average
;
/**平均点击率*/
private
String
avgHits
;
private
String
min
;
private
String
max
;
/**90% Line*/
private
String
tp90
;
/**95% Line*/
private
String
tp95
;
/**99% Line*/
private
String
tp99
;
/**最小请求时间 Min Response Time /ms */
private
String
min
;
private
String
transactions
;
/**最大请求时间 Max Response Time /ms */
private
String
max
;
private
String
received
;
/**吞吐量 KB/sec*/
private
String
kbPerSec
;
private
String
sent
;
/**错误率 Error Percentage */
private
String
errors
;
/**错误个数*/
private
Integer
ko
;
public
String
getRequestLabel
()
{
return
requestLabel
;
public
String
getLabel
()
{
return
label
;
}
public
void
set
RequestLabel
(
String
requestL
abel
)
{
this
.
requestLabel
=
requestL
abel
;
public
void
set
Label
(
String
l
abel
)
{
this
.
label
=
l
abel
;
}
public
Integer
getSamples
()
{
public
String
getSamples
()
{
return
samples
;
}
public
void
setSamples
(
Integer
samples
)
{
public
void
setSamples
(
String
samples
)
{
this
.
samples
=
samples
;
}
public
String
getKo
()
{
return
ko
;
}
public
void
setKo
(
String
ko
)
{
this
.
ko
=
ko
;
}
public
String
getError
()
{
return
error
;
}
public
void
setError
(
String
error
)
{
this
.
error
=
error
;
}
public
String
getAverage
()
{
return
average
;
}
...
...
@@ -62,12 +68,20 @@ public class RequestStatistics {
this
.
average
=
average
;
}
public
String
get
AvgHits
()
{
return
avgHits
;
public
String
get
Min
()
{
return
min
;
}
public
void
setAvgHits
(
String
avgHits
)
{
this
.
avgHits
=
avgHits
;
public
void
setMin
(
String
min
)
{
this
.
min
=
min
;
}
public
String
getMax
()
{
return
max
;
}
public
void
setMax
(
String
max
)
{
this
.
max
=
max
;
}
public
String
getTp90
()
{
...
...
@@ -94,43 +108,27 @@ public class RequestStatistics {
this
.
tp99
=
tp99
;
}
public
String
getMin
()
{
return
min
;
}
public
void
setMin
(
String
min
)
{
this
.
min
=
min
;
}
public
String
getMax
()
{
return
max
;
}
public
void
setMax
(
String
max
)
{
this
.
max
=
max
;
}
public
String
getKbPerSec
()
{
return
kbPerSec
;
public
String
getTransactions
()
{
return
transactions
;
}
public
void
set
KbPerSec
(
String
kbPerSec
)
{
this
.
kbPerSec
=
kbPerSec
;
public
void
set
Transactions
(
String
transactions
)
{
this
.
transactions
=
transactions
;
}
public
String
get
Errors
()
{
return
errors
;
public
String
get
Sent
()
{
return
sent
;
}
public
void
set
Errors
(
String
errors
)
{
this
.
errors
=
errors
;
public
void
set
Sent
(
String
sent
)
{
this
.
sent
=
sent
;
}
public
Integer
getKo
()
{
return
ko
;
public
String
getReceived
()
{
return
received
;
}
public
void
set
Ko
(
Integer
ko
)
{
this
.
ko
=
ko
;
public
void
set
Received
(
String
received
)
{
this
.
received
=
received
;
}
}
backend/src/main/java/io/metersphere/report/base/SummaryData.java
0 → 100644
浏览文件 @
b2d0e0cf
package
io.metersphere.report.base
;
import
java.util.List
;
public
class
SummaryData
{
private
List
<
Object
>
result
;
public
List
<
Object
>
getResult
()
{
return
result
;
}
public
void
setResult
(
List
<
Object
>
result
)
{
this
.
result
=
result
;
}
}
backend/src/main/java/io/metersphere/report/dto/ErrorsTop5DTO.java
已删除
100644 → 0
浏览文件 @
6aa69cee
package
io.metersphere.report.dto
;
import
io.metersphere.report.base.ErrorsTop5
;
import
java.util.List
;
public
class
ErrorsTop5DTO
{
private
List
<
ErrorsTop5
>
errorsTop5List
;
private
String
label
;
private
String
totalSamples
;
private
String
totalErrors
;
private
String
error1
;
private
String
error1Size
;
private
String
error2
;
private
String
error2Size
;
private
String
error3
;
private
String
error3Size
;
private
String
error4
;
private
String
error4Size
;
private
String
error5
;
private
String
error5Size
;
public
List
<
ErrorsTop5
>
getErrorsTop5List
()
{
return
errorsTop5List
;
}
public
void
setErrorsTop5List
(
List
<
ErrorsTop5
>
errorsTop5List
)
{
this
.
errorsTop5List
=
errorsTop5List
;
}
public
String
getLabel
()
{
return
label
;
}
public
void
setLabel
(
String
label
)
{
this
.
label
=
label
;
}
public
String
getTotalSamples
()
{
return
totalSamples
;
}
public
void
setTotalSamples
(
String
totalSamples
)
{
this
.
totalSamples
=
totalSamples
;
}
public
String
getTotalErrors
()
{
return
totalErrors
;
}
public
void
setTotalErrors
(
String
totalErrors
)
{
this
.
totalErrors
=
totalErrors
;
}
public
String
getError1
()
{
return
error1
;
}
public
void
setError1
(
String
error1
)
{
this
.
error1
=
error1
;
}
public
String
getError1Size
()
{
return
error1Size
;
}
public
void
setError1Size
(
String
error1Size
)
{
this
.
error1Size
=
error1Size
;
}
public
String
getError2
()
{
return
error2
;
}
public
void
setError2
(
String
error2
)
{
this
.
error2
=
error2
;
}
public
String
getError2Size
()
{
return
error2Size
;
}
public
void
setError2Size
(
String
error2Size
)
{
this
.
error2Size
=
error2Size
;
}
public
String
getError3
()
{
return
error3
;
}
public
void
setError3
(
String
error3
)
{
this
.
error3
=
error3
;
}
public
String
getError3Size
()
{
return
error3Size
;
}
public
void
setError3Size
(
String
error3Size
)
{
this
.
error3Size
=
error3Size
;
}
public
String
getError4
()
{
return
error4
;
}
public
void
setError4
(
String
error4
)
{
this
.
error4
=
error4
;
}
public
String
getError4Size
()
{
return
error4Size
;
}
public
void
setError4Size
(
String
error4Size
)
{
this
.
error4Size
=
error4Size
;
}
public
String
getError5
()
{
return
error5
;
}
public
void
setError5
(
String
error5
)
{
this
.
error5
=
error5
;
}
public
String
getError5Size
()
{
return
error5Size
;
}
public
void
setError5Size
(
String
error5Size
)
{
this
.
error5Size
=
error5Size
;
}
}
backend/src/main/java/io/metersphere/report/dto/RequestStatisticsDTO.java
已删除
100644 → 0
浏览文件 @
6aa69cee
package
io.metersphere.report.dto
;
import
io.metersphere.report.base.RequestStatistics
;
import
java.util.List
;
public
class
RequestStatisticsDTO
extends
RequestStatistics
{
private
List
<
RequestStatistics
>
requestStatisticsList
;
private
String
totalLabel
;
private
String
totalSamples
;
private
String
totalErrors
;
private
String
totalAverage
;
private
String
totalMin
;
private
String
totalMax
;
private
String
totalTP90
;
private
String
totalTP95
;
private
String
totalTP99
;
private
String
totalAvgBandwidth
;
private
String
totalAvgHits
;
public
List
<
RequestStatistics
>
getRequestStatisticsList
()
{
return
requestStatisticsList
;
}
public
void
setRequestStatisticsList
(
List
<
RequestStatistics
>
requestStatisticsList
)
{
this
.
requestStatisticsList
=
requestStatisticsList
;
}
public
String
getTotalLabel
()
{
return
totalLabel
;
}
public
void
setTotalLabel
(
String
totalLabel
)
{
this
.
totalLabel
=
totalLabel
;
}
public
String
getTotalSamples
()
{
return
totalSamples
;
}
public
void
setTotalSamples
(
String
totalSamples
)
{
this
.
totalSamples
=
totalSamples
;
}
public
String
getTotalErrors
()
{
return
totalErrors
;
}
public
void
setTotalErrors
(
String
totalErrors
)
{
this
.
totalErrors
=
totalErrors
;
}
public
String
getTotalAverage
()
{
return
totalAverage
;
}
public
void
setTotalAverage
(
String
totalAverage
)
{
this
.
totalAverage
=
totalAverage
;
}
public
String
getTotalMin
()
{
return
totalMin
;
}
public
void
setTotalMin
(
String
totalMin
)
{
this
.
totalMin
=
totalMin
;
}
public
String
getTotalMax
()
{
return
totalMax
;
}
public
void
setTotalMax
(
String
totalMax
)
{
this
.
totalMax
=
totalMax
;
}
public
String
getTotalTP90
()
{
return
totalTP90
;
}
public
void
setTotalTP90
(
String
totalTP90
)
{
this
.
totalTP90
=
totalTP90
;
}
public
String
getTotalTP95
()
{
return
totalTP95
;
}
public
void
setTotalTP95
(
String
totalTP95
)
{
this
.
totalTP95
=
totalTP95
;
}
public
String
getTotalTP99
()
{
return
totalTP99
;
}
public
void
setTotalTP99
(
String
totalTP99
)
{
this
.
totalTP99
=
totalTP99
;
}
public
String
getTotalAvgBandwidth
()
{
return
totalAvgBandwidth
;
}
public
void
setTotalAvgBandwidth
(
String
totalAvgBandwidth
)
{
this
.
totalAvgBandwidth
=
totalAvgBandwidth
;
}
public
String
getTotalAvgHits
()
{
return
totalAvgHits
;
}
public
void
setTotalAvgHits
(
String
totalAvgHits
)
{
this
.
totalAvgHits
=
totalAvgHits
;
}
}
backend/src/main/java/io/metersphere/report/parse/ResultDataParse.java
浏览文件 @
b2d0e0cf
...
...
@@ -7,6 +7,7 @@ import org.apache.jmeter.report.core.SampleMetadata;
import
org.apache.jmeter.report.dashboard.JsonizerVisitor
;
import
org.apache.jmeter.report.processor.*
;
import
org.apache.jmeter.report.processor.graph.AbstractOverTimeGraphConsumer
;
import
java.lang.reflect.Field
;
import
java.math.BigDecimal
;
import
java.text.ParseException
;
import
java.text.SimpleDateFormat
;
...
...
@@ -14,16 +15,49 @@ import java.time.Instant;
import
java.time.LocalDateTime
;
import
java.time.ZoneId
;
import
java.time.format.DateTimeFormatter
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.StringTokenizer
;
import
java.util.*
;
public
class
ResultDataParse
{
private
static
final
String
DATE_TIME_PATTERN
=
"yyyy/MM/dd HH:mm:ss"
;
private
static
final
String
TIME_PATTERN
=
"HH:mm:ss"
;
public
static
<
T
>
List
<
T
>
summaryMapParsing
(
Map
<
String
,
Object
>
map
,
Class
<
T
>
clazz
)
{
List
<
T
>
list
=
new
ArrayList
<>();
for
(
String
key
:
map
.
keySet
())
{
MapResultData
mapResultData
=
(
MapResultData
)
map
.
get
(
key
);
ListResultData
items
=
(
ListResultData
)
mapResultData
.
getResult
(
"items"
);
if
(
items
.
getSize
()
>
0
)
{
for
(
int
i
=
0
;
i
<
items
.
getSize
();
i
++)
{
MapResultData
resultData
=
(
MapResultData
)
items
.
get
(
i
);
ListResultData
data
=
(
ListResultData
)
resultData
.
getResult
(
"data"
);
int
size
=
data
.
getSize
();
String
[]
strArray
=
new
String
[
size
];
if
(
size
>
0
)
{
T
t
=
null
;
for
(
int
j
=
0
;
j
<
size
;
j
++)
{
ValueResultData
valueResultData
=
(
ValueResultData
)
data
.
get
(
j
);
if
(
valueResultData
.
getValue
()
==
null
)
{
strArray
[
j
]
=
""
;
}
else
{
String
accept
=
valueResultData
.
accept
(
new
JsonizerVisitor
());
strArray
[
j
]
=
accept
.
replace
(
"\\"
,
""
);
}
}
try
{
t
=
setParam
(
clazz
,
strArray
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
list
.
add
(
t
);
}
}
}
}
return
list
;
}
public
static
List
<
ChartsData
>
graphMapParsing
(
Map
<
String
,
Object
>
map
,
String
seriesName
)
{
List
<
ChartsData
>
list
=
new
ArrayList
<>();
// ThreadGroup
...
...
@@ -136,4 +170,21 @@ public class ResultDataParse {
SimpleDateFormat
after
=
new
SimpleDateFormat
(
TIME_PATTERN
);
return
after
.
format
(
before
.
parse
(
dateString
));
}
private
static
<
T
>
T
setParam
(
Class
<
T
>
clazz
,
Object
[]
args
)
throws
Exception
{
if
(
clazz
==
null
||
args
==
null
)
{
throw
new
IllegalArgumentException
();
}
T
t
=
clazz
.
newInstance
();
Field
[]
fields
=
clazz
.
getDeclaredFields
();
if
(
fields
==
null
||
fields
.
length
>
args
.
length
)
{
throw
new
IndexOutOfBoundsException
();
}
for
(
int
i
=
0
;
i
<
fields
.
length
;
i
++)
{
fields
[
i
].
setAccessible
(
true
);
fields
[
i
].
set
(
t
,
args
[
i
]);
}
return
t
;
}
}
backend/src/main/java/io/metersphere/service/PerformanceTestService.java
浏览文件 @
b2d0e0cf
...
...
@@ -24,6 +24,7 @@ import org.springframework.web.multipart.MultipartFile;
import
javax.annotation.Resource
;
import
java.io.IOException
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Optional
;
import
java.util.UUID
;
import
java.util.stream.Collectors
;
...
...
@@ -229,6 +230,23 @@ public class PerformanceTestService {
}
}
public
Map
<
String
,
String
>
log
(
String
testId
)
{
final
LoadTestWithBLOBs
loadTest
=
loadTestMapper
.
selectByPrimaryKey
(
testId
);
if
(
loadTest
==
null
)
{
MSException
.
throwException
(
Translator
.
get
(
"test_not_found"
)
+
testId
);
}
if
(!
StringUtils
.
equals
(
loadTest
.
getStatus
(),
PerformanceTestStatus
.
Running
.
name
()))
{
MSException
.
throwException
(
Translator
.
get
(
"test_not_running"
));
}
Engine
engine
=
EngineFactory
.
createEngine
(
loadTest
);
if
(
engine
==
null
)
{
MSException
.
throwException
(
String
.
format
(
"Engine is null,test ID:%s"
,
testId
));
}
return
engine
.
log
();
}
public
List
<
LoadTestDTO
>
recentTestPlans
(
QueryTestPlanRequest
request
)
{
// 查询最近的测试计划
request
.
setRecent
(
true
);
...
...
@@ -260,4 +278,5 @@ public class PerformanceTestService {
example
.
createCriteria
().
andTestResourcePoolIdEqualTo
(
resourcePoolId
);
return
loadTestMapper
.
selectByExampleWithBLOBs
(
example
);
}
}
backend/src/main/java/io/metersphere/service/ReportService.java
浏览文件 @
b2d0e0cf
...
...
@@ -12,16 +12,10 @@ import io.metersphere.dto.ReportDTO;
import
io.metersphere.engine.Engine
;
import
io.metersphere.engine.EngineFactory
;
import
io.metersphere.report.GenerateReport
;
import
io.metersphere.report.base.ChartsData
;
import
io.metersphere.report.base.Errors
;
import
io.metersphere.report.base.ReportTimeInfo
;
import
io.metersphere.report.base.TestOverview
;
import
io.metersphere.report.dto.ErrorsTop5DTO
;
import
io.metersphere.report.dto.RequestStatisticsDTO
;
import
io.metersphere.report.base.*
;
import
org.apache.commons.lang3.StringUtils
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
import
javax.annotation.Resource
;
import
java.util.List
;
...
...
@@ -86,12 +80,11 @@ public class ReportService {
return
extLoadTestReportMapper
.
getReportTestAndProInfo
(
reportId
);
}
public
RequestStatisticsDTO
getReport
(
String
id
)
{
public
List
<
Statistics
>
getReport
(
String
id
)
{
checkReportStatus
(
id
);
LoadTestReportWithBLOBs
loadTestReport
=
loadTestReportMapper
.
selectByPrimaryKey
(
id
);
String
content
=
loadTestReport
.
getContent
();
RequestStatisticsDTO
requestStatistics
=
GenerateReport
.
getRequestStatistics
(
content
);
return
requestStatistics
;
return
GenerateReport
.
getRequestStatistics
(
content
);
}
public
List
<
Errors
>
getReportErrors
(
String
id
)
{
...
...
@@ -102,12 +95,12 @@ public class ReportService {
return
errors
;
}
public
ErrorsTop5DTO
getReportErrorsTOP5
(
String
id
)
{
public
List
<
ErrorsTop5
>
getReportErrorsTOP5
(
String
id
)
{
checkReportStatus
(
id
);
LoadTestReportWithBLOBs
loadTestReport
=
loadTestReportMapper
.
selectByPrimaryKey
(
id
);
String
content
=
loadTestReport
.
getContent
();
ErrorsTop5DTO
errors
=
GenerateReport
.
getErrorsTop5DTO
(
content
);
return
errors
;
List
<
ErrorsTop5
>
errorsTop5
=
GenerateReport
.
getErrorsTop5List
(
content
);
return
errors
Top5
;
}
public
TestOverview
getTestOverview
(
String
id
)
{
...
...
@@ -119,7 +112,6 @@ public class ReportService {
}
public
ReportTimeInfo
getReportTimeInfo
(
String
id
)
{
checkReportStatus
(
id
);
LoadTestReportWithBLOBs
loadTestReport
=
loadTestReportMapper
.
selectByPrimaryKey
(
id
);
String
content
=
loadTestReport
.
getContent
();
ReportTimeInfo
reportTimeInfo
=
GenerateReport
.
getReportTimeInfo
(
content
);
...
...
backend/src/main/resources/i18n/messages_en_US.properties
浏览文件 @
b2d0e0cf
...
...
@@ -19,4 +19,6 @@ duplicate_node_ip=Duplicate IPs
only_one_k8s
=
Only one K8s can be added
organization_id_is_null
=
Organization ID cannot be null
max_thread_insufficient
=
The number of concurrent users exceeds
cannot_edit_load_test_running
=
Cannot modify the running test
\ No newline at end of file
cannot_edit_load_test_running
=
Cannot modify the running test
test_not_found
=
Test cannot be found:
test_not_running
=
Test is not running
\ No newline at end of file
backend/src/main/resources/i18n/messages_zh_CN.properties
浏览文件 @
b2d0e0cf
...
...
@@ -19,4 +19,6 @@ duplicate_node_ip=节点 IP 重复
only_one_k8s
=
只能添加一个 K8s
organization_id_is_null
=
组织 ID 不能为空
max_thread_insufficient
=
并发用户数超额
cannot_edit_load_test_running
=
不能修改正在运行的测试
\ No newline at end of file
cannot_edit_load_test_running
=
不能修改正在运行的测试
test_not_found
=
测试不存在:
test_not_running
=
测试未运行
\ No newline at end of file
backend/src/test/java/io/metersphere/JtlTest.java
浏览文件 @
b2d0e0cf
package
io.metersphere
;
import
com.alibaba.fastjson.JSONObject
;
import
com.opencsv.bean.CsvToBean
;
import
com.opencsv.bean.CsvToBeanBuilder
;
import
com.opencsv.bean.HeaderColumnNameMappingStrategy
;
import
io.metersphere.report.base.RequestStatistics
;
import
org.junit.Test
;
import
java.io.Reader
;
import
java.io.StringReader
;
...
...
@@ -201,73 +199,6 @@ public class JtlTest {
List
<
Metric
>
metrics
=
beanBuilderExample
(
jtlString
);
// 根据label分组,label作为map的key
Map
<
String
,
List
<
Metric
>>
map
=
metrics
.
stream
().
collect
(
Collectors
.
groupingBy
(
Metric:
:
getLabel
));
getOneRpsResult
(
map
);
}
private
void
getOneRpsResult
(
Map
<
String
,
List
<
Metric
>>
map
){
Iterator
<
Map
.
Entry
<
String
,
List
<
Metric
>>>
iterator
=
map
.
entrySet
().
iterator
();
while
(
iterator
.
hasNext
())
{
Map
.
Entry
<
String
,
List
<
Metric
>>
entry
=
iterator
.
next
();
String
label
=
entry
.
getKey
();
List
<
Metric
>
list
=
entry
.
getValue
();
List
<
String
>
timestampList
=
list
.
stream
().
map
(
Metric:
:
getTimestamp
).
collect
(
Collectors
.
toList
());
int
index
=
0
;
//总的响应时间
int
sumElapsed
=
0
;
Integer
failSize
=
0
;
Integer
totalBytes
=
0
;
// 响应时间的列表排序之后 用于计算90%line、95%line、99line
List
<
Integer
>
elapsedList
=
new
ArrayList
<>();
for
(
int
i
=
0
;
i
<
list
.
size
();
i
++)
{
try
{
Metric
row
=
list
.
get
(
i
);
//响应时间
String
elapsed
=
row
.
getElapsed
();
sumElapsed
+=
Integer
.
valueOf
(
elapsed
);
elapsedList
.
add
(
Integer
.
valueOf
(
elapsed
));
//成功与否
String
success
=
row
.
getSuccess
();
if
(!
"true"
.
equals
(
success
)){
failSize
++;
}
//字节
String
bytes
=
row
.
getBytes
();
totalBytes
+=
Integer
.
valueOf
(
bytes
);
index
++;
}
catch
(
Exception
e
){
System
.
out
.
println
(
"exception i:"
+
i
);
}
}
Collections
.
sort
(
elapsedList
,
new
Comparator
<
Integer
>()
{
public
int
compare
(
Integer
o1
,
Integer
o2
)
{
return
o1
-
o2
;
}
});
Integer
tp90
=
elapsedList
.
size
()*
9
/
10
;
Integer
tp95
=
elapsedList
.
size
()*
95
/
100
;
Integer
tp99
=
elapsedList
.
size
()*
99
/
100
;
Long
l
=
Long
.
valueOf
(
timestampList
.
get
(
index
-
1
))
-
Long
.
valueOf
(
timestampList
.
get
(
0
));
RequestStatistics
sceneResult
=
new
RequestStatistics
();
sceneResult
.
setRequestLabel
(
label
);
sceneResult
.
setSamples
(
index
);
// sceneResult.setAverage(sumElapsed/index);
sceneResult
.
setTp90
(
elapsedList
.
get
(
tp90
)+
""
);
sceneResult
.
setTp95
(
elapsedList
.
get
(
tp95
)+
""
);
sceneResult
.
setTp99
(
elapsedList
.
get
(
tp99
)+
""
);
sceneResult
.
setMin
(
elapsedList
.
get
(
0
)+
""
);
sceneResult
.
setMax
(
elapsedList
.
get
(
index
-
1
)+
""
);
sceneResult
.
setErrors
(
String
.
format
(
"%.2f"
,
failSize
*
100.0
/
index
)+
"%"
);
sceneResult
.
setKbPerSec
(
String
.
format
(
"%.2f"
,
totalBytes
*
1.0
/
1024
/(
l
*
1.0
/
1000
)));
System
.
out
.
println
(
JSONObject
.
toJSONString
(
sceneResult
));
System
.
out
.
println
();
}
}
}
backend/src/test/java/io/metersphere/ResultDataParseTest.java
0 → 100644
浏览文件 @
b2d0e0cf
package
io.metersphere
;
import
io.metersphere.report.base.Statistics
;
import
org.junit.Test
;
import
java.lang.reflect.Field
;
public
class
ResultDataParseTest
{
String
[]
s
=
{
"1"
,
"2"
,
"3"
,
"4"
,
"5"
,
"6"
,
"7"
,
"8"
,
"9"
,
"10"
,
"11"
,
"12"
,
"13"
};
public
static
<
T
>
T
setParam
(
Class
<
T
>
clazz
,
Object
[]
args
)
throws
Exception
{
if
(
clazz
==
null
||
args
==
null
)
{
throw
new
IllegalArgumentException
();
}
T
t
=
clazz
.
newInstance
();
Field
[]
fields
=
clazz
.
getDeclaredFields
();
if
(
fields
==
null
||
fields
.
length
>
args
.
length
)
{
throw
new
IndexOutOfBoundsException
();
}
for
(
int
i
=
0
;
i
<
fields
.
length
;
i
++)
{
fields
[
i
].
setAccessible
(
true
);
fields
[
i
].
set
(
t
,
args
[
i
]);
}
return
t
;
}
@Test
public
void
test
()
throws
Exception
{
Statistics
statistics
=
setParam
(
Statistics
.
class
,
s
);
System
.
out
.
println
(
statistics
.
toString
());
}
}
frontend/src/business/components/common/components/MsTableSearchBar.vue
0 → 100644
浏览文件 @
b2d0e0cf
<
template
>
<el-input
class=
"search"
type=
"text"
size=
"small"
:placeholder=
"$t('commons.search_by_name')"
prefix-icon=
"el-icon-search"
@
change=
"search"
maxlength=
"60"
v-model=
"condition.name"
clearable
/>
</
template
>
<
script
>
export
default
{
name
:
"
MsTableSearchBar
"
,
props
:
{
condition
:
{
type
:
Object
}
},
methods
:
{
search
()
{
this
.
$emit
(
'
update:condition
'
,
this
.
condition
);
this
.
$emit
(
'
change
'
);
}
}
}
</
script
>
<
style
scoped
>
</
style
>
frontend/src/business/components/common/components/MsTipButton.vue
浏览文件 @
b2d0e0cf
<
template
>
<el-tooltip
:disabled=
"disabled"
:content=
"tip"
placement=
"bottom"
:effect=
"effect"
>
<el-button
@
click=
"exec()"
circle
:type=
"type"
...
...
frontend/src/business/components/performance/report/PerformanceReportView.vue
浏览文件 @
b2d0e0cf
...
...
@@ -49,6 +49,13 @@
</el-tabs>
</el-card>
<el-dialog
:title=
"title"
:visible.sync=
"showTestLogging"
>
<el-tabs
type=
"border-card"
:stretch=
"true"
>
<el-tab-pane
v-for=
"(item, key) in testLogging"
:key=
"key"
:label=
"key"
class=
"logging-content"
>
{{
item
}}
</el-tab-pane>
</el-tabs>
</el-dialog>
</div>
</div>
</
template
>
...
...
@@ -79,15 +86,18 @@
startTime
:
'
0
'
,
endTime
:
'
0
'
,
minutes
:
'
0
'
,
seconds
:
'
0
'
seconds
:
'
0
'
,
title
:
'
Logging
'
,
testLogging
:
null
,
showTestLogging
:
false
,
}
},
methods
:
{
initBreadcrumb
()
{
if
(
this
.
reportId
)
{
if
(
this
.
reportId
)
{
this
.
result
=
this
.
$get
(
"
/performance/report/test/pro/info/
"
+
this
.
reportId
,
res
=>
{
let
data
=
res
.
data
;
if
(
data
)
{
if
(
data
)
{
this
.
reportName
=
data
.
name
;
this
.
testName
=
data
.
testName
;
this
.
projectName
=
data
.
projectName
;
...
...
@@ -96,10 +106,10 @@
}
},
initReportTimeInfo
()
{
if
(
this
.
reportId
)
{
if
(
this
.
reportId
)
{
this
.
result
=
this
.
$get
(
"
/performance/report/content/report_time/
"
+
this
.
reportId
,
res
=>
{
let
data
=
res
.
data
;
if
(
data
)
{
if
(
data
)
{
this
.
startTime
=
data
.
startTime
;
this
.
endTime
=
data
.
endTime
;
let
duration
=
data
.
duration
;
...
...
@@ -108,23 +118,37 @@
}
})
}
},
getLog
(
testId
)
{
this
.
result
=
this
.
$get
(
'
/performance/log/
'
+
testId
,
response
=>
{
this
.
testLogging
=
response
.
data
;
})
}
},
mounted
()
{
this
.
reportId
=
this
.
$route
.
path
.
split
(
'
/
'
)[
4
];
this
.
$get
(
"
/performance/report/
"
+
this
.
reportId
,
res
=>
{
this
.
result
=
this
.
$get
(
"
/performance/report/
"
+
this
.
reportId
,
res
=>
{
let
data
=
res
.
data
;
this
.
status
=
data
.
status
;
if
(
data
.
status
===
"
Error
"
)
{
this
.
$message
({
type
:
'
warning
'
,
message
:
"
报告生成错误,无法查看!
"
});
}
else
if
(
data
.
status
===
"
Starting
"
)
{
this
.
$message
({
type
:
'
info
'
,
message
:
"
报告生成中....
"
});
switch
(
data
.
status
)
{
case
'
Error
'
:
this
.
$message
({
type
:
'
warning
'
,
message
:
"
报告生成错误,无法查看!
"
});
break
;
case
'
Starting
'
:
this
.
$message
({
type
:
'
info
'
,
message
:
"
报告生成中....
"
});
break
;
case
'
Running
'
:
this
.
showTestLogging
=
true
;
this
.
getLog
(
data
.
testId
);
break
;
default
:
break
;
}
})
this
.
initBreadcrumb
();
...
...
@@ -133,10 +157,10 @@
watch
:
{
'
$route
'
(
to
)
{
let
reportId
=
to
.
path
.
split
(
'
/
'
)[
4
];
if
(
reportId
)
{
if
(
reportId
)
{
this
.
$get
(
"
/performance/report/test/pro/info/
"
+
reportId
,
response
=>
{
let
data
=
response
.
data
;
if
(
data
)
{
if
(
data
)
{
this
.
reportName
=
data
.
name
;
this
.
testName
=
data
.
testName
;
this
.
projectName
=
data
.
projectName
;
...
...
@@ -144,7 +168,7 @@
});
this
.
result
=
this
.
$get
(
"
/performance/report/content/report_time/
"
+
this
.
reportId
,
res
=>
{
let
data
=
res
.
data
;
if
(
data
)
{
if
(
data
)
{
this
.
startTime
=
data
.
startTime
;
this
.
endTime
=
data
.
endTime
;
let
duration
=
data
.
duration
;
...
...
@@ -170,4 +194,10 @@
display
:
block
;
color
:
#5C7878
;
}
.logging-content
{
white-space
:
pre-line
;
height
:
calc
(
100vh
-
450px
);
overflow
:
auto
;
}
</
style
>
frontend/src/business/components/performance/report/components/ErrorLog.vue
浏览文件 @
b2d0e0cf
...
...
@@ -39,7 +39,6 @@
stripe
style=
"width: 100%"
show-summary
:summary-method=
"getSummaries"
>
<el-table-column
prop=
"sample"
...
...
@@ -59,62 +58,65 @@
width=
"100"
>
</el-table-column>
<el-table-column
prop=
"error"
prop=
"error
1
"
label=
"Error"
width=
"400"
>
</el-table-column>
<el-table-column
prop=
"error
s
"
prop=
"error
1Size
"
label=
"#Errors"
width=
"100"
>
</el-table-column>
<el-table-column
prop=
"
Error
"
prop=
"
error2
"
label=
"Error"
width=
"400"
>
</el-table-column>
<el-table-column
prop=
"
#Errors
"
prop=
"
error2Size
"
label=
"#Errors"
width=
"100"
>
</el-table-column>
<el-table-column
prop=
"
Error
"
prop=
"
error3
"
label=
"Error"
width=
"400"
>
</el-table-column>
<el-table-column
prop=
"
#Errors
"
prop=
"
error3Size
"
label=
"#Errors"
width=
"100"
>
</el-table-column>
<el-table-column
prop=
"
Error
"
prop=
"
error4
"
label=
"Error"
width=
"400"
>
</el-table-column>
<el-table-column
prop=
"
#Errors
"
prop=
"
error4Size
"
label=
"#Errors"
width=
"100"
>
</el-table-column>
<el-table-column
prop=
"
Error
"
prop=
"
error5
"
label=
"Error"
width=
"400"
>
</el-table-column>
<el-table-column
prop=
"
#Errors
"
prop=
"
error5Size
"
label=
"#Errors"
width=
"100"
>
...
...
@@ -128,22 +130,7 @@
name
:
"
ErrorLog
"
,
data
()
{
return
{
tableData
:
[{},{},{},{},{}],
errorTotal
:
{
label
:
''
,
totalSamples
:
''
,
totalErrors
:
''
,
error1
:
''
,
error1Size
:
''
,
error2
:
''
,
error2Size
:
''
,
error3
:
''
,
error3Size
:
''
,
error4
:
''
,
error4Size
:
''
,
error5
:
''
,
error5Size
:
''
},
tableData
:
[],
errorTop5
:
[]
}
},
...
...
@@ -153,33 +140,14 @@
this
.
tableData
=
res
.
data
;
})
this
.
$get
(
"
/performance/report/content/errors_top5/
"
+
this
.
id
,
res
=>
{
this
.
errorTotal
=
res
.
data
this
.
errorTop5
=
res
.
data
.
errorsTop5List
;
this
.
errorTop5
=
res
.
data
;
})
},
getSummaries
()
{
const
sums
=
[]
sums
[
0
]
=
this
.
errorTotal
.
label
;
sums
[
1
]
=
this
.
errorTotal
.
totalSamples
;
sums
[
2
]
=
this
.
errorTotal
.
totalErrors
;
sums
[
3
]
=
this
.
errorTotal
.
error1
;
sums
[
4
]
=
this
.
errorTotal
.
error1Size
;
sums
[
5
]
=
this
.
errorTotal
.
error2
;
sums
[
6
]
=
this
.
errorTotal
.
error2Size
;
sums
[
7
]
=
this
.
errorTotal
.
error3
;
sums
[
8
]
=
this
.
errorTotal
.
error3Size
;
sums
[
9
]
=
this
.
errorTotal
.
error4
;
sums
[
10
]
=
this
.
errorTotal
.
error4Size
;
sums
[
11
]
=
this
.
errorTotal
.
error5
;
sums
[
12
]
=
this
.
errorTotal
.
error5Size
;
return
sums
;
}
},
watch
:
{
status
()
{
if
(
"
Completed
"
===
this
.
status
)
{
this
.
initTableData
()
this
.
getSummaries
()
}
}
},
...
...
frontend/src/business/components/performance/report/components/RequestStatistics.vue
浏览文件 @
b2d0e0cf
...
...
@@ -6,12 +6,11 @@
border
style=
"width: 100%"
show-summary
:summary-method=
"getSummaries"
:default-sort =
"
{prop: 'samples', order: 'descending'}"
>
<el-table-column
label=
"Requests"
fixed
width=
"450"
align=
"center"
>
<el-table-column
prop=
"
requestL
abel"
prop=
"
l
abel"
label=
"Label"
width=
"450"
/>
</el-table-column>
...
...
@@ -25,7 +24,13 @@
/>
<el-table-column
prop=
"errors"
prop=
"ko"
label=
"KO%"
align=
"center"
/>
<el-table-column
prop=
"error"
label=
"Error%"
align=
"center"
/>
...
...
@@ -58,18 +63,29 @@
/>
</el-table-column>
<el-table-column
prop=
"avgHits"
label=
"Avg Hits/s"
width=
"100"
/>
<el-table-column
label=
"Throughput"
>
<el-table-column
prop=
"transactions"
label=
"Transactions"
width=
"100"
/>
</el-table-column>
<el-table-column
label=
"NetWork(KB/sec)"
align=
"center"
>
<el-table-column
prop=
"received"
label=
"Received"
align=
"center"
width=
"200"
/>
<el-table-column
prop=
"sent"
label=
"Sent"
align=
"center"
width=
"200"
/>
</el-table-column>
<el-table-column
prop=
"kbPerSec"
label=
"Avg Bandwidth(KBytes/s)"
align=
"center"
width=
"200"
/>
</el-table>
</div>
</
template
>
...
...
@@ -79,50 +95,20 @@
name
:
"
RequestStatistics
"
,
data
()
{
return
{
tableData
:
[{},{},{},{},{}],
totalInfo
:
{
totalLabel
:
''
,
totalSamples
:
''
,
totalErrors
:
''
,
totalAverage
:
''
,
totalMin
:
''
,
totalMax
:
''
,
totalTP90
:
''
,
totalTP95
:
''
,
totalTP99
:
''
,
totalAvgHits
:
''
,
totalAvgBandwidth
:
''
}
tableData
:
[{},{},{},{},{}]
}
},
methods
:
{
initTableData
()
{
this
.
$get
(
"
/performance/report/content/
"
+
this
.
id
,
res
=>
{
this
.
tableData
=
res
.
data
.
requestStatisticsList
;
this
.
totalInfo
=
res
.
data
;
this
.
tableData
=
res
.
data
;
})
},
getSummaries
()
{
const
sums
=
[]
sums
[
0
]
=
this
.
totalInfo
.
totalLabel
;
sums
[
1
]
=
this
.
totalInfo
.
totalSamples
;
sums
[
2
]
=
this
.
totalInfo
.
totalErrors
;
sums
[
3
]
=
this
.
totalInfo
.
totalAverage
;
sums
[
4
]
=
this
.
totalInfo
.
totalMin
;
sums
[
5
]
=
this
.
totalInfo
.
totalMax
;
sums
[
6
]
=
this
.
totalInfo
.
totalTP90
;
sums
[
7
]
=
this
.
totalInfo
.
totalTP95
;
sums
[
8
]
=
this
.
totalInfo
.
totalTP99
;
sums
[
9
]
=
this
.
totalInfo
.
totalAvgHits
;
sums
[
10
]
=
this
.
totalInfo
.
totalAvgBandwidth
;
return
sums
;
}
},
watch
:
{
status
()
{
if
(
"
Completed
"
===
this
.
status
)
{
this
.
initTableData
()
this
.
getSummaries
()
}
}
},
...
...
frontend/src/business/components/track/case/TestCase.vue
浏览文件 @
b2d0e0cf
...
...
@@ -9,16 +9,19 @@
@
dataChange=
"changeProject"
>
</select-menu>
<node-tree
class=
"node-tree"
:current-project=
"currentProject"
@
nodeSelectEvent=
"refreshTable"
@
refresh=
"refreshTable"
ref=
"nodeTree"
>
</node-tree>
v-loading=
"result.loading"
@
nodeSelectEvent=
"nodeChange"
@
refresh=
"refresh"
:tree-nodes=
"treeNodes"
:type=
"'edit'"
ref=
"nodeTree"
/>
</el-aside>
<el-main
class=
"test-case-list"
>
<test-case-list
:current-project=
"currentProject"
:selectNodeIds=
"selectNodeIds"
:selectNodeNames=
"selectNodeNames"
@
openTestCaseEditDialog=
"openTestCaseEditDialog"
@
testCaseEdit=
"openTestCaseEditDialog"
@
refresh=
"refresh"
...
...
@@ -29,17 +32,17 @@
<test-case-edit
@
refresh=
"refreshTable"
:tree-nodes=
"treeNodes"
ref=
"testCaseEditDialog"
>
</test-case-edit>
</div>
</div>
</
template
>
<
script
>
import
NodeTree
from
'
.
/components
/NodeTree
'
;
import
NodeTree
from
'
.
./common
/NodeTree
'
;
import
TestCaseEdit
from
'
./components/TestCaseEdit
'
;
import
{
CURRENT_PROJECT
,
WORKSPACE_ID
}
from
'
../../../../common/js/constants
'
;
import
{
CURRENT_PROJECT
}
from
'
../../../../common/js/constants
'
;
import
TestCaseList
from
"
./components/TestCaseList
"
;
import
SelectMenu
from
"
../common/SelectMenu
"
;
...
...
@@ -50,20 +53,19 @@
data
()
{
return
{
result
:
{},
tableData
:
[],
multipleSelection
:
[],
currentPage
:
1
,
pageSize
:
5
,
total
:
0
,
projects
:
[],
currentProject
:
null
,
treeNodes
:
[]
treeNodes
:
[],
selectNodeIds
:
[],
selectNodeNames
:
[]
}
},
created
()
{
this
.
getProjects
();
},
mounted
()
{
this
.
getProjects
();
this
.
refresh
();
if
(
this
.
$route
.
params
.
projectId
){
this
.
getProjectById
(
this
.
$route
.
params
.
projectId
)
}
...
...
@@ -77,11 +79,16 @@
let
path
=
to
.
path
;
if
(
to
.
params
.
projectId
){
this
.
getProjectById
(
to
.
params
.
projectId
)
this
.
getProjects
();
}
if
(
path
.
indexOf
(
"
/track/case/edit
"
)
>=
0
){
this
.
openRecentTestCaseEditDialog
();
this
.
$router
.
push
(
'
/track/case/all
'
);
this
.
getProjects
();
}
},
currentProject
()
{
this
.
refresh
();
}
},
methods
:
{
...
...
@@ -90,7 +97,7 @@
this
.
projects
=
response
.
data
;
let
lastProject
=
JSON
.
parse
(
localStorage
.
getItem
(
CURRENT_PROJECT
));
if
(
lastProject
)
{
let
hasCurrentProject
=
false
;
let
hasCurrentProject
=
false
;
for
(
let
i
=
0
;
i
<
this
.
projects
.
length
;
i
++
)
{
if
(
this
.
projects
[
i
].
id
==
lastProject
.
id
)
{
this
.
currentProject
=
lastProject
;
...
...
@@ -122,37 +129,15 @@
changeProject
(
project
)
{
this
.
setCurrentProject
(
project
);
},
refreshTable
(
data
)
{
this
.
$refs
.
testCaseList
.
initTableData
(
data
);
},
openTestCaseEditDialog
(
data
)
{
this
.
setNodePathOption
(
this
.
$refs
.
nodeTree
.
treeNodes
);
this
.
setMaintainerOptions
();
this
.
$refs
.
testCaseEditDialog
.
openTestCaseEditDialog
(
data
);
nodeChange
(
nodeIds
,
nodeNames
)
{
this
.
selectNodeIds
=
nodeIds
;
this
.
selectNodeNames
=
nodeNames
;
},
setNodePathOption
(
nodes
)
{
let
moduleOptions
=
[];
nodes
.
forEach
(
node
=>
{
this
.
buildNodePath
(
node
,
{
path
:
''
},
moduleOptions
);
});
this
.
$refs
.
testCaseEditDialog
.
moduleOptions
=
moduleOptions
;
},
buildNodePath
(
node
,
option
,
moduleOptions
)
{
//递归构建节点路径
option
.
id
=
node
.
id
;
option
.
path
=
option
.
path
+
'
/
'
+
node
.
name
;
moduleOptions
.
push
(
option
);
if
(
node
.
children
)
{
for
(
let
i
=
0
;
i
<
node
.
children
.
length
;
i
++
){
this
.
buildNodePath
(
node
.
children
[
i
],
{
path
:
option
.
path
},
moduleOptions
);
}
}
refreshTable
()
{
this
.
$refs
.
testCaseList
.
initTableData
();
},
setMaintainerOptions
()
{
let
workspaceId
=
localStorage
.
getItem
(
WORKSPACE_ID
);
this
.
$post
(
'
/user/ws/member/list/all
'
,
{
workspaceId
:
workspaceId
},
response
=>
{
this
.
$refs
.
testCaseEditDialog
.
maintainerOptions
=
response
.
data
;
});
openTestCaseEditDialog
(
testCase
)
{
this
.
$refs
.
testCaseEditDialog
.
open
(
testCase
);
},
getProjectByCaseId
(
caseId
)
{
return
this
.
$get
(
'
/test/case/project/
'
+
caseId
,
async
response
=>
{
...
...
@@ -160,9 +145,10 @@
});
},
refresh
()
{
this
.
selectNodeIds
=
[];
this
.
selectNodeNames
=
[];
this
.
$refs
.
testCaseList
.
initTableData
();
this
.
$refs
.
nodeTree
.
getNodeTree
();
this
.
getProjects
();
this
.
getNodeTree
();
},
openRecentTestCaseEditDialog
()
{
let
caseId
=
this
.
$route
.
params
.
caseId
;
...
...
@@ -190,8 +176,14 @@
localStorage
.
setItem
(
CURRENT_PROJECT
,
JSON
.
stringify
(
project
));
}
this
.
refresh
();
},
getNodeTree
()
{
if
(
this
.
currentProject
)
{
this
.
result
=
this
.
$get
(
"
/case/node/list/
"
+
this
.
currentProject
.
id
,
response
=>
{
this
.
treeNodes
=
response
.
data
;
});
}
}
}
}
</
script
>
...
...
@@ -216,13 +208,8 @@
margin-left
:
0
;
}
.main-content
{
/*background: white;*/
}
.test-case-list
{
padding
:
15px
;
}
</
style
>
frontend/src/business/components/track/case/components/TestCaseEdit.vue
浏览文件 @
b2d0e0cf
...
...
@@ -167,18 +167,11 @@
</el-form>
<
template
v-slot:footer
>
<div
class=
"dialog-footer"
>
<el-button
@
click=
"dialogFormVisible = false"
>
{{
$t
(
'
test_track.cancel
'
)
}}
</el-button>
<el-button
type=
"primary"
@
click=
"saveCase"
>
{{
$t
(
'
test_track.confirm
'
)
}}
</el-button>
</div>
<ms-dialog-footer
@
cancel=
"dialogFormVisible = false"
@
confirm=
"saveCase"
/>
</
template
>
</el-dialog>
</div>
...
...
@@ -188,126 +181,163 @@
<
script
>
import
{
CURRENT_PROJECT
}
from
'
../../../../../common/js/constants
'
;
import
{
CURRENT_PROJECT
,
WORKSPACE_ID
}
from
'
../../../../../common/js/constants
'
;
import
MsDialogFooter
from
'
../../../common/components/MsDialogFooter
'
export
default
{
name
:
"
TestCaseEdit
"
,
data
()
{
return
{
dialogFormVisible
:
false
,
form
:
{
name
:
''
,
module
:
''
,
maintainer
:
''
,
priority
:
''
,
type
:
''
,
method
:
''
,
prerequisite
:
''
,
steps
:
[{
num
:
1
,
desc
:
''
,
result
:
''
}],
remark
:
''
,
},
moduleOptions
:
[],
maintainerOptions
:
[],
rules
:{
name
:[
{
required
:
true
,
message
:
this
.
$t
(
'
test_track.case.input_name
'
),
trigger
:
'
blur
'
},
{
max
:
30
,
message
:
this
.
$t
(
'
test_track.length_less_than
'
)
+
'
30
'
,
trigger
:
'
blur
'
}
],
module
:[{
required
:
true
,
message
:
this
.
$t
(
'
test_track.case.input_module
'
),
trigger
:
'
change
'
}],
maintainer
:[{
required
:
true
,
message
:
this
.
$t
(
'
test_track.case.input_maintainer
'
),
trigger
:
'
change
'
}],
priority
:[{
required
:
true
,
message
:
this
.
$t
(
'
test_track.case.input_priority
'
),
trigger
:
'
change
'
}],
type
:[{
required
:
true
,
message
:
this
.
$t
(
'
test_track.case.input_type
'
),
trigger
:
'
change
'
}],
method
:[{
required
:
true
,
message
:
this
.
$t
(
'
test_track.case.input_method
'
),
trigger
:
'
change
'
}]
},
formLabelWidth
:
"
120px
"
,
operationType
:
''
};
},
methods
:
{
openTestCaseEditDialog
(
testCase
)
{
this
.
resetForm
();
this
.
operationType
=
'
add
'
;
if
(
testCase
){
//修改
this
.
operationType
=
'
edit
'
;
let
tmp
=
{};
Object
.
assign
(
tmp
,
testCase
);
tmp
.
steps
=
JSON
.
parse
(
testCase
.
steps
);
Object
.
assign
(
this
.
form
,
tmp
);
this
.
form
.
module
=
testCase
.
nodeId
;
}
this
.
dialogFormVisible
=
true
;
},
handleAddStep
(
index
,
data
)
{
let
step
=
{};
step
.
num
=
data
.
num
+
1
;
step
.
desc
=
null
;
step
.
result
=
null
;
this
.
form
.
steps
.
forEach
(
step
=>
{
if
(
step
.
num
>
data
.
num
){
step
.
num
++
;
}
});
this
.
form
.
steps
.
push
(
step
);
name
:
"
TestCaseEdit
"
,
components
:
{
MsDialogFooter
},
data
()
{
return
{
dialogFormVisible
:
false
,
form
:
{
name
:
''
,
module
:
''
,
maintainer
:
''
,
priority
:
''
,
type
:
''
,
method
:
''
,
prerequisite
:
''
,
steps
:
[{
num
:
1
,
desc
:
''
,
result
:
''
}],
remark
:
''
,
},
handleDeleteStep
(
index
,
data
)
{
this
.
form
.
steps
.
splice
(
index
,
1
);
this
.
form
.
steps
.
forEach
(
step
=>
{
if
(
step
.
num
>
data
.
num
){
step
.
num
--
;
}
});
moduleOptions
:
[],
maintainerOptions
:
[],
workspaceId
:
''
,
rules
:{
name
:[
{
required
:
true
,
message
:
this
.
$t
(
'
test_track.case.input_name
'
),
trigger
:
'
blur
'
},
{
max
:
30
,
message
:
this
.
$t
(
'
test_track.length_less_than
'
)
+
'
30
'
,
trigger
:
'
blur
'
}
],
module
:[{
required
:
true
,
message
:
this
.
$t
(
'
test_track.case.input_module
'
),
trigger
:
'
change
'
}],
maintainer
:[{
required
:
true
,
message
:
this
.
$t
(
'
test_track.case.input_maintainer
'
),
trigger
:
'
change
'
}],
priority
:[{
required
:
true
,
message
:
this
.
$t
(
'
test_track.case.input_priority
'
),
trigger
:
'
change
'
}],
type
:[{
required
:
true
,
message
:
this
.
$t
(
'
test_track.case.input_type
'
),
trigger
:
'
change
'
}],
method
:[{
required
:
true
,
message
:
this
.
$t
(
'
test_track.case.input_method
'
),
trigger
:
'
change
'
}]
},
saveCase
(){
this
.
$refs
[
'
caseFrom
'
].
validate
((
valid
)
=>
{
if
(
valid
)
{
let
param
=
{};
Object
.
assign
(
param
,
this
.
form
);
param
.
steps
=
JSON
.
stringify
(
this
.
form
.
steps
);
param
.
nodeId
=
this
.
form
.
module
;
this
.
moduleOptions
.
forEach
(
item
=>
{
if
(
this
.
form
.
module
===
item
.
id
){
param
.
nodePath
=
item
.
path
;
}
});
if
(
localStorage
.
getItem
(
CURRENT_PROJECT
))
{
param
.
projectId
=
JSON
.
parse
(
localStorage
.
getItem
(
CURRENT_PROJECT
)).
id
;
formLabelWidth
:
"
120px
"
,
operationType
:
''
};
},
props
:
{
treeNodes
:
{
type
:
Array
}
},
methods
:
{
open
(
testCase
)
{
this
.
resetForm
();
this
.
getSelectOptions
();
this
.
operationType
=
'
add
'
;
if
(
testCase
){
//修改
this
.
operationType
=
'
edit
'
;
let
tmp
=
{};
Object
.
assign
(
tmp
,
testCase
);
tmp
.
steps
=
JSON
.
parse
(
testCase
.
steps
);
Object
.
assign
(
this
.
form
,
tmp
);
this
.
form
.
module
=
testCase
.
nodeId
;
}
this
.
dialogFormVisible
=
true
;
},
handleAddStep
(
index
,
data
)
{
let
step
=
{};
step
.
num
=
data
.
num
+
1
;
step
.
desc
=
null
;
step
.
result
=
null
;
this
.
form
.
steps
.
forEach
(
step
=>
{
if
(
step
.
num
>
data
.
num
){
step
.
num
++
;
}
});
this
.
form
.
steps
.
push
(
step
);
},
handleDeleteStep
(
index
,
data
)
{
this
.
form
.
steps
.
splice
(
index
,
1
);
this
.
form
.
steps
.
forEach
(
step
=>
{
if
(
step
.
num
>
data
.
num
){
step
.
num
--
;
}
});
},
saveCase
(){
this
.
$refs
[
'
caseFrom
'
].
validate
((
valid
)
=>
{
if
(
valid
)
{
let
param
=
{};
Object
.
assign
(
param
,
this
.
form
);
param
.
steps
=
JSON
.
stringify
(
this
.
form
.
steps
);
param
.
nodeId
=
this
.
form
.
module
;
this
.
moduleOptions
.
forEach
(
item
=>
{
if
(
this
.
form
.
module
===
item
.
id
){
param
.
nodePath
=
item
.
path
;
}
this
.
$post
(
'
/test/case/
'
+
this
.
operationType
,
param
,
()
=>
{
this
.
$message
.
success
(
this
.
$t
(
'
commons.save_success
'
));
this
.
dialogFormVisible
=
false
;
this
.
$emit
(
"
refresh
"
);
});
}
else
{
return
false
;
});
if
(
localStorage
.
getItem
(
CURRENT_PROJECT
))
{
param
.
projectId
=
JSON
.
parse
(
localStorage
.
getItem
(
CURRENT_PROJECT
)).
id
;
}
});
}
,
resetForm
()
{
if
(
this
.
$refs
[
'
caseFrom
'
])
{
this
.
$refs
[
'
caseFrom
'
].
resetFields
();
this
.
$post
(
'
/test/case/
'
+
this
.
operationType
,
param
,
()
=>
{
this
.
$message
.
success
(
this
.
$t
(
'
commons.save_success
'
));
this
.
dialogFormVisible
=
false
;
this
.
$emit
(
"
refresh
"
);
});
}
else
{
return
false
;
}
});
},
getModuleOptions
()
{
let
moduleOptions
=
[];
this
.
treeNodes
.
forEach
(
node
=>
{
this
.
buildNodePath
(
node
,
{
path
:
''
},
moduleOptions
);
});
this
.
moduleOptions
=
moduleOptions
;
},
getMaintainerOptions
()
{
let
workspaceId
=
localStorage
.
getItem
(
WORKSPACE_ID
);
this
.
$post
(
'
/user/ws/member/list/all
'
,
{
workspaceId
:
workspaceId
},
response
=>
{
this
.
maintainerOptions
=
response
.
data
;
});
},
getSelectOptions
()
{
this
.
getModuleOptions
();
this
.
getMaintainerOptions
();
},
buildNodePath
(
node
,
option
,
moduleOptions
)
{
//递归构建节点路径
option
.
id
=
node
.
id
;
option
.
path
=
option
.
path
+
'
/
'
+
node
.
name
;
moduleOptions
.
push
(
option
);
if
(
node
.
children
)
{
for
(
let
i
=
0
;
i
<
node
.
children
.
length
;
i
++
){
this
.
buildNodePath
(
node
.
children
[
i
],
{
path
:
option
.
path
},
moduleOptions
);
}
this
.
form
.
name
=
''
;
this
.
form
.
module
=
''
;
this
.
form
.
type
=
''
;
this
.
form
.
method
=
''
;
this
.
form
.
maintainer
=
''
;
this
.
form
.
priority
=
''
;
this
.
form
.
prerequisite
=
''
;
this
.
form
.
remark
=
''
;
this
.
form
.
steps
=
[{
num
:
1
,
desc
:
''
,
result
:
''
}];
}
},
resetForm
()
{
if
(
this
.
$refs
[
'
caseFrom
'
])
{
this
.
$refs
[
'
caseFrom
'
].
resetFields
();
}
this
.
form
.
name
=
''
;
this
.
form
.
module
=
''
;
this
.
form
.
type
=
''
;
this
.
form
.
method
=
''
;
this
.
form
.
maintainer
=
''
;
this
.
form
.
priority
=
''
;
this
.
form
.
prerequisite
=
''
;
this
.
form
.
remark
=
''
;
this
.
form
.
steps
=
[{
num
:
1
,
desc
:
''
,
result
:
''
}];
}
}
}
</
script
>
<
style
scoped
>
...
...
frontend/src/business/components/track/case/components/TestCaseImport.vue
浏览文件 @
b2d0e0cf
...
...
@@ -37,7 +37,8 @@
<li
v-for=
"errFile in errList"
:key=
"errFile.rowNum"
>
{{errFile.errMsg}}
</li>
</ul></el-row>
</ul>
</el-row>
</el-dialog>
...
...
frontend/src/business/components/track/case/components/TestCaseList.vue
浏览文件 @
b2d0e0cf
...
...
@@ -5,20 +5,18 @@
<template
v-slot:header
>
<div>
<el-row
type=
"flex"
justify=
"space-between"
align=
"middle"
>
<span
class=
"title"
>
{{
$t
(
'
test_track.case.test_case
'
)
}}
<ms-create-box
:tips=
"$t('test_track.case.create')"
:exec=
"testCaseCreate"
/></span>
<node-breadcrumb
:node-names=
"selectNodeNames"
@
refresh=
"refresh"
/>
<span
class=
"operate-button"
>
<ms-create-box
:tips=
"$t('test_track.case.create')"
:exec=
"testCaseCreate"
/>
<test-case-import
:projectId=
"currentProject == null? null : currentProject.id"
@
refresh=
"refresh"
/>
<test-case-export/>
<el-input
type=
"text"
size=
"small"
class=
"search"
:placeholder=
"$t('load_test.search_by_name')"
prefix-icon=
"el-icon-search"
maxlength=
"60"
v-model=
"condition"
@
change=
"search"
clearable
/></span>
<ms-table-search-bar
:condition.sync=
"condition"
@
change=
"initTableData"
/>
</span>
</el-row>
</div>
</
template
>
...
...
@@ -94,16 +92,17 @@
import
TestCaseImport
from
'
../components/TestCaseImport
'
;
import
TestCaseExport
from
'
../components/TestCaseExport
'
;
import
MsTablePagination
from
'
../../../../components/common/pagination/TablePagination
'
;
import
MsTableSearchBar
from
'
../../../../components/common/components/MsTableSearchBar
'
;
import
NodeBreadcrumb
from
'
../../common/NodeBreadcrumb
'
;
export
default
{
name
:
"
TestCaseList
"
,
components
:
{
MsCreateBox
,
TestCaseImport
,
TestCaseExport
,
MsTablePagination
},
components
:
{
MsCreateBox
,
TestCaseImport
,
TestCaseExport
,
MsTablePagination
,
NodeBreadcrumb
,
MsTableSearchBar
},
data
()
{
return
{
result
:
{},
deletePath
:
"
/test/case/delete
"
,
condition
:
""
,
condition
:
{}
,
tableData
:
[],
currentPage
:
1
,
pageSize
:
5
,
...
...
@@ -113,6 +112,12 @@
props
:
{
currentProject
:
{
type
:
Object
},
selectNodeIds
:
{
type
:
Array
},
selectNodeNames
:
{
type
:
Array
}
},
created
:
function
()
{
...
...
@@ -121,18 +126,17 @@
watch
:
{
currentProject
()
{
this
.
initTableData
();
},
selectNodeIds
()
{
this
.
initTableData
();
}
},
methods
:
{
initTableData
(
nodeIds
)
{
let
param
=
{
name
:
this
.
condition
,
};
param
.
nodeIds
=
nodeIds
;
initTableData
()
{
this
.
condition
.
nodeIds
=
this
.
selectNodeIds
;
if
(
this
.
currentProject
)
{
param
.
projectId
=
this
.
currentProject
.
id
;
this
.
result
=
this
.
$post
(
this
.
buildPagePath
(
'
/test/case/list
'
),
param
,
response
=>
{
this
.
condition
.
projectId
=
this
.
currentProject
.
id
;
this
.
result
=
this
.
$post
(
this
.
buildPagePath
(
'
/test/case/list
'
),
this
.
condition
,
response
=>
{
let
data
=
response
.
data
;
this
.
total
=
data
.
itemCount
;
this
.
tableData
=
data
.
listObject
;
...
...
@@ -172,6 +176,7 @@
});
},
refresh
()
{
this
.
condition
=
{};
this
.
$emit
(
'
refresh
'
);
}
}
...
...
frontend/src/business/components/track/common/NodeBreadcrumb.vue
0 → 100644
浏览文件 @
b2d0e0cf
<
template
>
<el-breadcrumb
separator-class=
"el-icon-arrow-right"
>
<el-breadcrumb-item>
<a
@
click=
"showAll"
>
<i
class=
"el-icon-s-home"
></i>
{{
$t
(
'
test_track.plan_view.all_case
'
)
}}
</a>
</el-breadcrumb-item>
<el-breadcrumb-item
v-for=
"nodeName in data"
:key=
"nodeName"
>
{{
nodeName
}}
</el-breadcrumb-item>
</el-breadcrumb>
</
template
>
<
script
>
export
default
{
name
:
"
NodeBreadcrumb
"
,
data
()
{
return
{
data
:
[]
}
},
props
:
{
nodeNames
:
{
type
:
Array
}
},
watch
:
{
nodeNames
()
{
this
.
filterData
();
}
},
methods
:
{
showAll
()
{
this
.
$emit
(
'
refresh
'
);
},
filterData
()
{
this
.
data
=
this
.
nodeNames
;
if
(
this
.
data
.
length
>
4
)
{
let
lastData
=
this
.
data
[
this
.
data
.
length
-
1
];
this
.
data
.
splice
(
1
,
this
.
data
.
length
);
this
.
data
.
push
(
'
...
'
);
this
.
data
.
push
(
lastData
);
}
}
}
}
</
script
>
<
style
scoped
>
</
style
>
frontend/src/business/components/track/common/NodeEdit.vue
0 → 100644
浏览文件 @
b2d0e0cf
<
template
>
<el-dialog
:title=
"$t('test_track.module.add_module')"
:visible.sync=
"dialogFormVisible"
width=
"30%"
>
<el-row
type=
"flex"
justify=
"center"
>
<el-col
:span=
"18"
>
<el-form
:model=
"form"
:rules=
"rules"
>
<el-form-item
:label=
"$t('test_track.module.name')"
:label-width=
"formLabelWidth"
prop=
"name"
>
<el-input
v-model=
"form.name"
autocomplete=
"off"
></el-input>
</el-form-item>
</el-form>
</el-col>
</el-row>
<template
v-slot:footer
>
<ms-dialog-footer
@
cancel=
"dialogFormVisible = false"
@
confirm=
"saveNode"
/>
</
template
>
</el-dialog>
</template>
<
script
>
import
{
CURRENT_PROJECT
}
from
'
../../../../common/js/constants
'
;
import
MsDialogFooter
from
'
../../common/components/MsDialogFooter
'
;
export
default
{
components
:
{
MsDialogFooter
},
data
()
{
return
{
name
:
"
NodeEdit
"
,
form
:
{
name
:
''
,
},
rules
:{
name
:[
{
required
:
true
,
message
:
this
.
$t
(
'
test_track.case.input_name
'
),
trigger
:
'
blur
'
},
{
max
:
30
,
message
:
this
.
$t
(
'
test_track.length_less_than
'
)
+
'
30
'
,
trigger
:
'
blur
'
}
]
},
type
:
''
,
node
:
{},
formLabelWidth
:
'
80px
'
,
dialogFormVisible
:
false
,
}
},
methods
:
{
open
(
type
,
data
)
{
this
.
type
=
type
;
this
.
node
=
data
;
this
.
dialogFormVisible
=
true
;
},
saveNode
()
{
let
param
=
{};
let
url
=
this
.
buildParam
(
param
);
this
.
$post
(
url
,
param
,
()
=>
{
this
.
$message
.
success
(
this
.
$t
(
'
commons.save_success
'
));
this
.
$emit
(
'
refresh
'
);
this
.
close
();
});
},
buildParam
(
param
,
)
{
let
url
=
''
;
if
(
this
.
type
===
'
add
'
)
{
url
=
'
/case/node/add
'
;
param
.
level
=
1
;
if
(
this
.
node
)
{
//非根节点
param
.
pId
=
this
.
node
.
id
;
param
.
level
=
this
.
node
.
level
+
1
;
}
}
else
if
(
this
.
type
===
'
edit
'
)
{
url
=
'
/case/node/edit
'
;
param
.
id
=
this
.
node
.
id
}
param
.
name
=
this
.
form
.
name
;
param
.
label
=
this
.
form
.
name
;
if
(
localStorage
.
getItem
(
CURRENT_PROJECT
))
{
param
.
projectId
=
JSON
.
parse
(
localStorage
.
getItem
(
CURRENT_PROJECT
)).
id
;
}
return
url
;
},
close
()
{
this
.
form
.
name
=
''
;
this
.
dialogFormVisible
=
false
;
}
}
}
</
script
>
<
style
scoped
>
</
style
>
frontend/src/business/components/track/c
ase/components
/NodeTree.vue
→
frontend/src/business/components/track/c
ommon
/NodeTree.vue
浏览文件 @
b2d0e0cf
...
...
@@ -3,7 +3,7 @@
<div
v-loading=
"result.loading"
>
<el-input
:placeholder=
"$t('test_track.module.search')"
v-model=
"filterText"
size=
"small"
>
<template
v-slot:append
>
<template
v-
if=
"type == 'edit'"
v-
slot:append
>
<el-button
icon=
"el-icon-folder-add"
@
click=
"openEditNodeDialog('add')"
></el-button>
</
template
>
</el-input>
...
...
@@ -20,47 +20,37 @@
ref=
"tree"
>
<
template
v-slot:default=
"{node,data}"
>
<span
class=
"custom-tree-node father"
@
click=
"selectNode(node)"
>
<span>
{{
node
.
label
}}
</span>
<el-dropdown
class=
"node-dropdown child"
>
<el-dropdown
v-if=
"type == 'edit'"
class=
"node-dropdown child"
>
<span
class=
"el-dropdown-link"
>
<i
class=
"el-icon-folder-add"
></i>
</span>
<el-dropdown-menu
v-slot:default
>
<el-dropdown-item>
<div
@
click=
"openEditNodeDialog('edit', data)"
>
{{
$t
(
'
test_track.module.rename
'
)
}}
</div>
</el-dropdown-item>
<el-dropdown-item>
<div
@
click=
"openEditNodeDialog('add', data)"
>
{{
$t
(
'
test_track.module.add_submodule
'
)
}}
</div>
</el-dropdown-item>
<el-dropdown-item>
<div
@
click=
"remove(node, data)"
>
{{
$t
(
'
commons.delete
'
)
}}
</div>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-dropdown-menu
v-slot:default
>
<el-dropdown-item>
<div
@
click=
"openEditNodeDialog('edit', data)"
>
{{
$t
(
'
test_track.module.rename
'
)
}}
</div>
</el-dropdown-item>
<el-dropdown-item>
<div
@
click=
"openEditNodeDialog('add', data)"
>
{{
$t
(
'
test_track.module.add_submodule
'
)
}}
</div>
</el-dropdown-item>
<el-dropdown-item>
<div
@
click=
"remove(node, data)"
>
{{
$t
(
'
commons.delete
'
)
}}
</div>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</span>
<!--
<span
v-if=
"type == 'view'"
class=
"custom-tree-node"
@
click=
"selectNode(node)"
>
-->
<!--
{{
node
.
label
}}
$$-->
<!--
</span>
-->
</
template
>
</el-tree>
<el-dialog
:title=
"$t('test_track.module.add_module')"
:visible.sync=
"dialogFormVisible"
width=
"500px"
>
<el-row
type=
"flex"
justify=
"center"
>
<el-col
:span=
"18"
>
<el-form
:model=
"form"
>
<el-form-item
:label=
"$t('test_track.module.name')"
:label-width=
"formLabelWidth"
>
<el-input
v-model=
"form.name"
autocomplete=
"off"
></el-input>
</el-form-item>
</el-form>
</el-col>
</el-row>
<
template
v-slot:footer
>
<div
class=
"dialog-footer"
>
<el-button
@
click=
"dialogFormVisible = false"
>
{{
$t
(
'
test_track.cancel
'
)
}}
</el-button>
<el-button
type=
"primary"
@
click=
"editNode"
>
{{
$t
(
'
test_track.confirm
'
)
}}
</el-button>
</div>
</
template
>
</el-dialog>
<node-edit
ref=
"nodeEdit"
@
refresh=
"refreshNode"
/>
</div>
...
...
@@ -68,10 +58,11 @@
<
script
>
import
{
CURRENT_PROJECT
}
from
'
../../../../../common/js/constants
'
;
import
NodeEdit
from
'
./NodeEdit
'
;
export
default
{
name
:
"
NodeTree
"
,
components
:
{
NodeEdit
},
data
()
{
return
{
result
:
{},
...
...
@@ -80,34 +71,23 @@
children
:
'
children
'
,
label
:
'
label
'
},
form
:
{
name
:
''
,
},
formLabelWidth
:
'
80px
'
,
dialogTableVisible
:
false
,
dialogFormVisible
:
false
,
editType
:
''
,
editData
:
{},
treeNodes
:
[],
defaultKeys
:
[]
// treeNodes: [],
};
},
props
:
{
currentProject
:
{
type
:
Object
type
:
{
type
:
String
,
default
:
'
view
'
},
treeNodes
:
{
type
:
Array
}
},
watch
:
{
filterText
(
val
)
{
this
.
$refs
.
tree
.
filter
(
val
);
},
currentProject
()
{
this
.
getNodeTree
();
}
},
created
()
{
this
.
getNodeTree
();
},
methods
:
{
handleDragEnd
(
draggingNode
,
dropNode
,
dropType
,
ev
)
{
let
param
=
{};
...
...
@@ -148,8 +128,10 @@
},
selectNode
(
node
)
{
let
nodeIds
=
[];
let
nodeNames
=
[];
this
.
getChildNodeId
(
node
,
nodeIds
);
this
.
$emit
(
"
nodeSelectEvent
"
,
nodeIds
);
this
.
getParentNodeName
(
node
,
nodeNames
);
this
.
$emit
(
"
nodeSelectEvent
"
,
nodeIds
,
nodeNames
);
},
getChildNodeId
(
rootNode
,
nodeIds
)
{
//递归获取所有子节点ID
...
...
@@ -157,69 +139,25 @@
for
(
let
i
=
0
;
i
<
rootNode
.
childNodes
.
length
;
i
++
)
{
this
.
getChildNodeId
(
rootNode
.
childNodes
[
i
],
nodeIds
);
}
return
nodeIds
;
},
getParentNodeName
(
rootNode
,
nodeNames
)
{
if
(
rootNode
.
parent
&&
rootNode
.
parent
.
id
!=
0
)
{
this
.
getParentNodeName
(
rootNode
.
parent
,
nodeNames
)
}
if
(
rootNode
.
data
.
name
&&
rootNode
.
data
.
name
!=
''
)
{
nodeNames
.
push
(
rootNode
.
data
.
name
);
}
},
filterNode
(
value
,
data
)
{
if
(
!
value
)
return
true
;
return
data
.
label
.
indexOf
(
value
)
!==
-
1
;
},
editNode
()
{
this
.
saveNode
(
this
.
editType
,
this
.
editData
);
this
.
dialogFormVisible
=
false
;
},
openEditNodeDialog
(
type
,
data
)
{
this
.
editType
=
type
;
this
.
editData
=
data
;
this
.
dialogFormVisible
=
true
;
},
getNodeTree
()
{
if
(
this
.
currentProject
)
{
let
projectId
=
this
.
currentProject
.
id
;
this
.
result
=
this
.
$get
(
"
/case/node/list/
"
+
projectId
,
response
=>
{
this
.
treeNodes
=
response
.
data
;
});
}
},
saveNode
(
type
,
pNode
)
{
let
param
=
{};
let
url
=
''
;
if
(
type
===
'
add
'
)
{
url
=
'
/case/node/add
'
;
param
.
level
=
1
;
if
(
pNode
)
{
//非根节点
param
.
pId
=
pNode
.
id
;
param
.
level
=
pNode
.
level
+
1
;
}
}
else
if
(
type
===
'
edit
'
)
{
url
=
'
/case/node/edit
'
;
param
.
id
=
this
.
editData
.
id
}
param
.
name
=
this
.
form
.
name
;
param
.
label
=
this
.
form
.
name
;
if
(
localStorage
.
getItem
(
CURRENT_PROJECT
))
{
param
.
projectId
=
JSON
.
parse
(
localStorage
.
getItem
(
CURRENT_PROJECT
)).
id
;
}
this
.
$post
(
url
,
param
,
response
=>
{
if
(
type
===
'
edit
'
)
{
this
.
editData
.
label
=
param
.
label
;
}
if
(
type
===
'
add
'
)
{
param
.
id
=
response
.
data
;
if
(
pNode
)
{
this
.
$refs
.
tree
.
append
(
param
,
pNode
);
}
else
{
this
.
treeNodes
.
push
(
param
);
}
}
this
.
$message
.
success
(
this
.
$t
(
'
commons.save_success
'
));
});
this
.
form
.
name
=
''
;
this
.
$refs
.
nodeEdit
.
open
(
type
,
data
);
},
refreshNode
()
{
this
.
$emit
(
'
refresh
'
);
}
}
}
</
script
>
...
...
frontend/src/business/components/track/
plan/components
/PlanNodeTree.vue
→
frontend/src/business/components/track/
common
/PlanNodeTree.vue
浏览文件 @
b2d0e0cf
...
...
@@ -105,8 +105,10 @@
},
selectNode
(
node
)
{
let
nodeIds
=
[];
let
nodeNames
=
[];
this
.
getChildNodeId
(
node
,
nodeIds
);
this
.
$emit
(
"
nodeSelectEvent
"
,
nodeIds
);
this
.
getParentNodeName
(
node
,
nodeNames
);
this
.
$emit
(
"
nodeSelectEvent
"
,
nodeIds
,
nodeNames
);
},
getChildNodeId
(
rootNode
,
nodeIds
)
{
//递归获取所有子节点ID
...
...
@@ -114,7 +116,14 @@
for
(
let
i
=
0
;
i
<
rootNode
.
childNodes
.
length
;
i
++
){
this
.
getChildNodeId
(
rootNode
.
childNodes
[
i
],
nodeIds
);
}
return
nodeIds
;
},
getParentNodeName
(
rootNode
,
nodeNames
)
{
if
(
rootNode
.
parent
&&
rootNode
.
parent
.
id
!=
0
)
{
this
.
getParentNodeName
(
rootNode
.
parent
,
nodeNames
)
}
if
(
rootNode
.
data
.
name
&&
rootNode
.
data
.
name
!=
''
)
{
nodeNames
.
push
(
rootNode
.
data
.
name
);
}
},
filterNode
(
value
,
data
)
{
if
(
!
value
)
return
true
;
...
...
frontend/src/business/components/track/plan/TestPlanView.vue
浏览文件 @
b2d0e0cf
...
...
@@ -6,16 +6,14 @@
:data=
"testPlans"
:current-data=
"currentPlan"
:title=
"$t('test_track.plan_view.plan')"
@
dataChange=
"changePlan"
>
</select-menu>
<plan-node-tree
class=
"node-tree"
:plan-id=
"planId"
@
nodeSelectEvent=
"getPlanCases"
ref=
"tree"
>
</plan-node-tree>
@
dataChange=
"changePlan"
/>
<node-tree
class=
"node-tree"
v-loading=
"result.loading"
@
nodeSelectEvent=
"nodeChange"
@
refresh=
"refresh"
:tree-nodes=
"treeNodes"
ref=
"nodeTree"
/>
</el-aside>
<el-main>
...
...
@@ -23,33 +21,38 @@
@
openTestCaseRelevanceDialog=
"openTestCaseRelevanceDialog"
@
refresh=
"refresh"
:plan-id=
"planId"
ref=
"testCasePlanList"
></test-plan-test-case-list>
:select-node-ids=
"selectNodeIds"
:select-node-names=
"selectNodeNames"
ref=
"testCasePlanList"
/>
</el-main>
</el-container>
<test-case-relevance
@
refresh=
"refresh"
:plan-id=
"planId"
ref=
"testCaseRelevance"
>
</test-case-relevance>
ref=
"testCaseRelevance"
/>
</div>
</
template
>
<
script
>
import
PlanNodeTree
from
"
./components/Plan
NodeTree
"
;
import
NodeTree
from
"
../common/
NodeTree
"
;
import
TestPlanTestCaseList
from
"
./components/TestPlanTestCaseList
"
;
import
TestCaseRelevance
from
"
./components/TestCaseRelevance
"
;
import
SelectMenu
from
"
../common/SelectMenu
"
;
export
default
{
name
:
"
TestPlanView
"
,
components
:
{
Plan
NodeTree
,
TestPlanTestCaseList
,
TestCaseRelevance
,
SelectMenu
},
components
:
{
NodeTree
,
TestPlanTestCaseList
,
TestCaseRelevance
,
SelectMenu
},
data
()
{
return
{
result
:
{},
testPlans
:
[],
currentPlan
:
{}
currentPlan
:
{},
selectNodeIds
:
[],
selectNodeNames
:
[],
treeNodes
:
[]
}
},
computed
:
{
...
...
@@ -57,21 +60,23 @@
return
this
.
$route
.
params
.
planId
;
}
},
crea
ted
()
{
this
.
getTestPlans
();
moun
ted
()
{
this
.
initData
();
},
watch
:
{
planId
()
{
this
.
getTestPlans
();
this
.
initData
();
}
},
methods
:
{
refresh
()
{
this
.
getPlanCases
();
this
.
$refs
.
tree
.
initTree
();
this
.
selectNodeIds
=
[];
this
.
selectNodeNames
=
[];
this
.
getNodeTreeByPlanId
();
},
getPlanCases
(
nodeIds
)
{
this
.
$refs
.
testCasePlanList
.
initTableData
(
nodeIds
);
initData
()
{
this
.
getTestPlans
();
this
.
getNodeTreeByPlanId
();
},
openTestCaseRelevanceDialog
()
{
this
.
$refs
.
testCaseRelevance
.
openTestCaseRelevanceDialog
();
...
...
@@ -86,9 +91,20 @@
});
});
},
nodeChange
(
nodeIds
,
nodeNames
)
{
this
.
selectNodeIds
=
nodeIds
;
this
.
selectNodeNames
=
nodeNames
;
},
changePlan
(
plan
)
{
this
.
currentPlan
=
plan
;
this
.
$router
.
push
(
'
/track/plan/view/
'
+
plan
.
id
);
},
getNodeTreeByPlanId
()
{
if
(
this
.
planId
){
this
.
result
=
this
.
$get
(
"
/case/node/list/plan/
"
+
this
.
planId
,
response
=>
{
this
.
treeNodes
=
response
.
data
;
});
}
}
}
}
...
...
@@ -117,8 +133,4 @@
padding
:
15px
;
}
.main-content
{
/*background: white;*/
}
</
style
>
frontend/src/business/components/track/plan/components/TestCaseRelevance.vue
浏览文件 @
b2d0e0cf
...
...
@@ -9,12 +9,11 @@
<el-container
class=
"main-content"
>
<el-aside
class=
"node-tree"
width=
"250px"
>
<plan-node-tree
:tree-nodes=
"treeNodes"
:plan-id=
"planId"
:showAll=
true
@
nodeSelectEvent=
"getCaseNameByNodeIds"
ref=
"tree"
></plan-node-tree>
<node-tree
class=
"node-tree"
@
nodeSelectEvent=
"nodeChange"
@
refresh=
"refresh"
:tree-nodes=
"treeNodes"
ref=
"nodeTree"
/>
</el-aside>
<el-container>
...
...
@@ -45,32 +44,22 @@
</el-container>
<
template
v-slot:footer
>
<div
class=
"dialog-footer"
>
<el-button
@
click=
"dialogFormVisible = false"
>
{{
$t
(
'
test_track.cancel
'
)
}}
</el-button>
<el-button
type=
"primary"
@
click=
"saveCaseRelevance"
>
{{
$t
(
'
test_track.confirm
'
)
}}
</el-button>
</div>
<ms-dialog-footer
@
cancel=
"dialogFormVisible = false"
@
confirm=
"saveCaseRelevance"
/>
</
template
>
</el-dialog>
</el-dialog>
</div>
</template>
<
script
>
import
PlanNodeTree
from
'
./PlanNodeTree
'
;
import
NodeTree
from
'
../../common/NodeTree
'
;
import
MsDialogFooter
from
'
../../../common/components/MsDialogFooter
'
export
default
{
export
default
{
name
:
"
TestCaseRelevance
"
,
components
:
{
PlanNodeTree
},
components
:
{
NodeTree
,
MsDialogFooter
},
data
()
{
return
{
result
:
{},
...
...
@@ -78,7 +67,9 @@
isCheckAll
:
false
,
testCases
:
[],
selectIds
:
new
Set
(),
treeNodes
:
[]
treeNodes
:
[],
selectNodeIds
:
[],
selectNodeNames
:
[]
};
},
props
:
{
...
...
@@ -88,12 +79,15 @@
},
watch
:
{
planId
()
{
this
.
initData
();
},
selectNodeIds
()
{
this
.
getCaseNames
();
}
},
methods
:
{
openTestCaseRelevanceDialog
()
{
this
.
getCaseNames
();
this
.
initData
();
this
.
dialogFormVisible
=
true
;
},
saveCaseRelevance
(){
...
...
@@ -107,13 +101,13 @@
this
.
$emit
(
'
refresh
'
);
});
},
getCaseNames
(
nodeIds
)
{
getCaseNames
()
{
let
param
=
{};
if
(
this
.
planId
)
{
param
.
planId
=
this
.
planId
;
}
if
(
nodeIds
&&
n
odeIds
.
length
>
0
){
param
.
nodeIds
=
n
odeIds
;
if
(
this
.
selectNodeIds
&&
this
.
selectN
odeIds
.
length
>
0
){
param
.
nodeIds
=
this
.
selectN
odeIds
;
}
this
.
result
=
this
.
$post
(
'
/test/case/name
'
,
param
,
response
=>
{
this
.
testCases
=
response
.
data
;
...
...
@@ -122,10 +116,6 @@
});
});
},
getCaseNameByNodeIds
(
nodeIds
)
{
this
.
dialogFormVisible
=
true
;
this
.
getCaseNames
(
nodeIds
);
},
handleSelectAll
(
selection
)
{
if
(
selection
.
length
>
0
){
this
.
testCases
.
forEach
(
item
=>
{
...
...
@@ -142,8 +132,28 @@
this
.
selectIds
.
add
(
row
.
id
);
}
},
nodeChange
(
nodeIds
,
nodeNames
)
{
this
.
selectNodeIds
=
nodeIds
;
this
.
selectNodeNames
=
nodeNames
;
},
initData
()
{
this
.
getCaseNames
();
this
.
getAllNodeTreeByPlanId
();
},
refresh
()
{
this
.
close
();
},
getAllNodeTreeByPlanId
()
{
if
(
this
.
planId
)
{
this
.
result
=
this
.
$get
(
"
/case/node/list/all/plan/
"
+
this
.
planId
,
response
=>
{
this
.
treeNodes
=
response
.
data
;
});
}
},
close
()
{
this
.
selectIds
.
clear
();
this
.
selectNodeIds
=
[];
this
.
selectNodeNames
=
[];
}
}
}
...
...
frontend/src/business/components/track/plan/components/TestPlanTestCaseEdit.vue
浏览文件 @
b2d0e0cf
<
template
>
<el-drawer
:before-close=
"handleClose"
:visible.sync=
"showDialog"
:with-header=
"false"
size=
"100%"
ref=
"drawer"
>
<template
v-slot:default=
"scope"
>
<el-header>
<el-row
type=
"flex"
class=
"head-bar"
>
<el-col
:span=
"12"
>
<el-button
plain
size=
"mini"
icon=
"el-icon-back"
@
click=
"cancel"
>
{{
$t
(
'
test_track.return
'
)
}}
</el-button>
</el-col>
<el-col
:span=
"12"
class=
"head-right"
>
<span
class=
"head-right-tip"
v-if=
"index + 1 == tableData.length"
>
{{
$t
(
'
test_track.plan_view.pre_case
'
)
}}
:
{{
tableData
?
tableData
[
index
-
1
].
name
:
''
}}
</span>
<span
class=
"head-right-tip"
v-if=
"index + 1
<
tableData.length
"
>
{{
$t
(
'
test_track.plan_view.next_case
'
)
}}
:
{{
tableData
?
tableData
[
index
+
1
].
name
:
''
}}
</span>
<el-button
plain
size=
"mini"
icon=
"el-icon-arrow-up"
:disabled=
"index + 1
<
=
1"
@
click=
"handlePre()"
/>
<span>
{{
index
+
1
}}
/
{{
tableData
.
length
}}
</span>
<el-button
plain
size=
"mini"
icon=
"el-icon-arrow-down"
:disabled=
"index + 1 >= tableData.length"
@
click=
"handleNext()"
/>
<el-divider
direction=
"vertical"
></el-divider>
<el-button
type=
"primary"
size=
"mini"
@
click=
"saveCase"
>
{{
$t
(
'
test_track.save
'
)
}}
</el-button>
</el-col>
</el-row>
<el-row
style=
"margin-top: 0px;"
>
<el-col>
<el-divider
content-position=
"left"
>
{{
testCase
.
name
}}
</el-divider>
</el-col>
</el-row>
</el-header>
<div
class=
"case_container"
>
<el-row
>
<el-col
:span=
"4"
:offset=
"1"
>
<span
class=
"cast_label"
>
{{
$t
(
'
test_track.case.priority
'
)
}}
:
</span>
<span
class=
"cast_item"
>
{{
testCase
.
priority
}}
</span>
</el-col>
<el-col
:span=
"5"
>
<span
class=
"cast_label"
>
{{
$t
(
'
test_track.case.case_type
'
)
}}
:
</span>
<span
class=
"cast_item"
v-if=
"testCase.type == 'functional'"
>
{{
$t
(
'
commons.functional
'
)
}}
</span>
<span
class=
"cast_item"
v-if=
"testCase.type == 'performance'"
>
{{
$t
(
'
commons.performance
'
)
}}
</span>
<span
class=
"cast_item"
v-if=
"testCase.type == 'api'"
>
{{
$t
(
'
commons.api
'
)
}}
</span>
</el-col>
<el-col
:span=
"13"
>
<test-plan-test-case-status-button
class=
"status-button"
@
statusChange=
"statusChange"
:status=
"testCase.status"
/>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"4"
:offset=
"1"
>
<span
class=
"cast_label"
>
{{
$t
(
'
test_track.case.method
'
)
}}
:
</span>
<span
v-if=
"testCase.method == 'manual'"
>
{{
$t
(
'
test_track.case.manual
'
)
}}
</span>
<span
v-if=
"testCase.method == 'auto'"
>
{{
$t
(
'
test_track.case.auto
'
)
}}
</span>
</el-col>
<el-col
:span=
"5"
>
<span
class=
"cast_label"
>
{{
$t
(
'
test_track.case.module
'
)
}}
:
</span>
<span
class=
"cast_item"
>
{{
testCase
.
nodePath
}}
</span>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"20"
:offset=
"1"
>
<div>
<span
class=
"cast_label"
>
{{
$t
(
'
test_track.case.steps
'
)
}}
:
</span>
</div>
<el-table
:data=
"testCase.steptResults"
class=
"tb-edit"
size=
"mini"
height=
"250px"
:border=
"true"
:default-sort =
"
{prop: 'num', order: 'ascending'}"
highlight-current-row>
<el-table-column
:label=
"$t('test_track.case.number')"
prop=
"num"
min-width=
"5%"
></el-table-column>
<el-table-column
:label=
"$t('test_track.case.step_desc')"
prop=
"desc"
min-width=
"29%"
>
<template
v-slot:default=
"scope"
>
<span>
{{
scope
.
row
.
desc
}}
</span>
</
template
>
</el-table-column>
<el-table-column
:label=
"$t('test_track.case.expected_results')"
prop=
"result"
min-width=
"28%"
>
<
template
v-slot:default=
"scope"
>
<span>
{{
scope
.
row
.
result
}}
</span>
</
template
>
</el-table-column>
<el-table-column
:label=
"$t('test_track.plan_view.actual_result')"
min-width=
"29%"
>
<
template
v-slot:default=
"scope"
>
<el-input
ref=
"drawer"
v-loading=
"result.loading"
>
<template
v-slot:default=
"scope"
>
<div
class=
"container"
>
<el-scrollbar>
<el-header>
<el-row
type=
"flex"
class=
"head-bar"
>
<el-col
:span=
"12"
>
<el-button
plain
size=
"mini"
icon=
"el-icon-back"
@
click=
"cancel"
>
{{
$t
(
'
test_track.return
'
)
}}
</el-button>
</el-col>
<el-col
:span=
"12"
class=
"head-right"
>
<span
class=
"head-right-tip"
v-if=
"index + 1 == testCases.length"
>
{{
$t
(
'
test_track.plan_view.pre_case
'
)
}}
:
{{
testCases
[
index
-
1
]
?
testCases
[
index
-
1
].
name
:
''
}}
</span>
<span
class=
"head-right-tip"
v-if=
"index + 1 != testCases.length"
>
{{
$t
(
'
test_track.plan_view.next_case
'
)
}}
:
{{
testCases
[
index
+
1
]
?
testCases
[
index
+
1
].
name
:
''
}}
</span>
<el-button
plain
size=
"mini"
icon=
"el-icon-arrow-up"
:disabled=
"index + 1
<
=
1"
@
click=
"handlePre()"
/>
<span>
{{
index
+
1
}}
/
{{
testCases
.
length
}}
</span>
<el-button
plain
size=
"mini"
icon=
"el-icon-arrow-down"
:disabled=
"index + 1 >= testCases.length"
@
click=
"handleNext()"
/>
<el-divider
direction=
"vertical"
></el-divider>
<el-button
type=
"primary"
size=
"mini"
@
click=
"saveCase"
>
{{
$t
(
'
test_track.save
'
)
}}
</el-button>
</el-col>
</el-row>
<el-row
style=
"margin-top: 0px;"
>
<el-col>
<el-divider
content-position=
"left"
>
{{
testCase
.
name
}}
</el-divider>
</el-col>
</el-row>
</el-header>
<div
class=
"case_container"
>
<el-row
>
<el-col
:span=
"4"
:offset=
"1"
>
<span
class=
"cast_label"
>
{{
$t
(
'
test_track.case.priority
'
)
}}
:
</span>
<span
class=
"cast_item"
>
{{
testCase
.
priority
}}
</span>
</el-col>
<el-col
:span=
"5"
>
<span
class=
"cast_label"
>
{{
$t
(
'
test_track.case.case_type
'
)
}}
:
</span>
<span
class=
"cast_item"
v-if=
"testCase.type == 'functional'"
>
{{
$t
(
'
commons.functional
'
)
}}
</span>
<span
class=
"cast_item"
v-if=
"testCase.type == 'performance'"
>
{{
$t
(
'
commons.performance
'
)
}}
</span>
<span
class=
"cast_item"
v-if=
"testCase.type == 'api'"
>
{{
$t
(
'
commons.api
'
)
}}
</span>
</el-col>
<el-col
:span=
"13"
>
<test-plan-test-case-status-button
class=
"status-button"
@
statusChange=
"statusChange"
:status=
"testCase.status"
/>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"4"
:offset=
"1"
>
<span
class=
"cast_label"
>
{{
$t
(
'
test_track.case.method
'
)
}}
:
</span>
<span
v-if=
"testCase.method == 'manual'"
>
{{
$t
(
'
test_track.case.manual
'
)
}}
</span>
<span
v-if=
"testCase.method == 'auto'"
>
{{
$t
(
'
test_track.case.auto
'
)
}}
</span>
</el-col>
<el-col
:span=
"5"
>
<span
class=
"cast_label"
>
{{
$t
(
'
test_track.case.module
'
)
}}
:
</span>
<span
class=
"cast_item"
>
{{
testCase
.
nodePath
}}
</span>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"20"
:offset=
"1"
>
<div>
<span
class=
"cast_label"
>
{{
$t
(
'
test_track.case.steps
'
)
}}
:
</span>
</div>
<el-table
:data=
"testCase.steptResults"
class=
"tb-edit"
size=
"mini"
type=
"textarea"
:rows=
"2"
v-model=
"scope.row.actualResult"
:placeholder=
"$t('commons.input_content')"
clearable
></el-input>
<span>
{{
scope
.
row
.
actualResult
}}
</span>
</
template
>
</el-table-column>
<el-table-column
:label=
"$t('test_track.plan_view.step_result')"
min-width=
"9%"
>
<
template
v-slot:default=
"scope"
>
<el-select
v-model=
"scope.row.executeResult"
size=
"mini"
>
<el-option
:label=
"$t('test_track.plan_view.pass')"
value=
"Pass"
style=
"color: #7ebf50;"
></el-option>
<el-option
:label=
"$t('test_track.plan_view.failure')"
value=
"Failure"
style=
"color: #e57471;"
></el-option>
<el-option
:label=
"$t('test_track.plan_view.blocking')"
value=
"Blocking"
style=
"color: #dda451;"
></el-option>
<el-option
:label=
"$t('test_track.plan_view.skip')"
value=
"Skip"
style=
"color: #919399;"
></el-option>
</el-select>
</
template
>
</el-table-column>
</el-table>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"15"
:offset=
"1"
>
<div>
<span
class=
"cast_label"
>
{{$t('commons.remark')}}:
</span>
<span
v-if=
"testCase.remark == null || testCase.remark == ''"
style=
"color: darkgrey"
>
{{$t('commons.not_filled')}}
</span>
</div>
<div>
<el-input
:rows=
"3"
type=
"textarea"
v-if=
"testCase.remark"
disabled
v-model=
"testCase.remark"
></el-input>
:border=
"true"
:default-sort =
"
{prop: 'num', order: 'ascending'}"
highlight-current-row>
<el-table-column
:label=
"$t('test_track.case.number')"
prop=
"num"
min-width=
"5%"
></el-table-column>
<el-table-column
:label=
"$t('test_track.case.step_desc')"
prop=
"desc"
min-width=
"29%"
>
<template
v-slot:default=
"scope"
>
<span>
{{
scope
.
row
.
desc
}}
</span>
</
template
>
</el-table-column>
<el-table-column
:label=
"$t('test_track.case.expected_results')"
prop=
"result"
min-width=
"28%"
>
<
template
v-slot:default=
"scope"
>
<span>
{{
scope
.
row
.
result
}}
</span>
</
template
>
</el-table-column>
<el-table-column
:label=
"$t('test_track.plan_view.actual_result')"
min-width=
"29%"
>
<
template
v-slot:default=
"scope"
>
<el-input
size=
"mini"
type=
"textarea"
:rows=
"2"
v-model=
"scope.row.actualResult"
:placeholder=
"$t('commons.input_content')"
clearable
></el-input>
<span>
{{
scope
.
row
.
actualResult
}}
</span>
</
template
>
</el-table-column>
<el-table-column
:label=
"$t('test_track.plan_view.step_result')"
min-width=
"9%"
>
<
template
v-slot:default=
"scope"
>
<el-select
v-model=
"scope.row.executeResult"
size=
"mini"
>
<el-option
:label=
"$t('test_track.plan_view.pass')"
value=
"Pass"
style=
"color: #7ebf50;"
></el-option>
<el-option
:label=
"$t('test_track.plan_view.failure')"
value=
"Failure"
style=
"color: #e57471;"
></el-option>
<el-option
:label=
"$t('test_track.plan_view.blocking')"
value=
"Blocking"
style=
"color: #dda451;"
></el-option>
<el-option
:label=
"$t('test_track.plan_view.skip')"
value=
"Skip"
style=
"color: #919399;"
></el-option>
</el-select>
</
template
>
</el-table-column>
</el-table>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"15"
:offset=
"1"
>
<div>
<span
class=
"cast_label"
>
{{$t('commons.remark')}}:
</span>
<span
v-if=
"testCase.remark == null || testCase.remark == ''"
style=
"color: darkgrey"
>
{{$t('commons.not_filled')}}
</span>
</div>
<div>
<el-input
:rows=
"3"
type=
"textarea"
v-if=
"testCase.remark"
disabled
v-model=
"testCase.remark"
></el-input>
</div>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
</el-scrollbar>
</div>
</template>
</div>
</template>
</el-drawer>
</template>
<
script
>
...
...
@@ -163,17 +172,19 @@
components
:
{
TestPlanTestCaseStatusButton
},
data
()
{
return
{
result
:
{},
showDialog
:
false
,
testCase
:
{},
index
:
0
index
:
0
,
testCases
:
[]
};
},
props
:
{
tableData
:
{
type
:
Array
},
total
:
{
type
:
Number
},
searchParam
:
{
type
:
Object
}
},
methods
:
{
...
...
@@ -213,7 +224,7 @@
this
.
getTestCase
(
this
.
index
);
},
getTestCase
(
index
)
{
let
testCase
=
this
.
t
ableData
[
index
];
let
testCase
=
this
.
t
estCases
[
index
];
let
item
=
{};
Object
.
assign
(
item
,
testCase
);
item
.
results
=
JSON
.
parse
(
item
.
results
);
...
...
@@ -227,6 +238,22 @@
item
.
steptResults
.
push
(
item
.
steps
[
i
]);
}
this
.
testCase
=
item
;
},
openTestCaseEdit
(
testCase
)
{
this
.
showDialog
=
true
;
this
.
initData
(
testCase
);
},
initData
(
testCase
)
{
this
.
result
=
this
.
$post
(
'
/test/plan/case/list/all
'
,
this
.
searchParam
,
response
=>
{
this
.
testCases
=
response
.
data
;
for
(
let
i
=
0
;
i
<
this
.
testCases
.
length
;
i
++
)
{
if
(
this
.
testCases
[
i
].
id
===
testCase
.
id
)
{
this
.
index
=
i
;
this
.
getTestCase
(
i
);
}
}
});
}
}
}
...
...
@@ -265,12 +292,20 @@
float
:
right
;
}
.head-bar
{
margin-top
:
1%
;
}
.head-right-tip
{
color
:
darkgrey
;
}
.el-scrollbar
{
height
:
100%
;
}
.container
{
height
:
100vh
;
}
.case_container
>
.el-row
{
margin-top
:
1%
;
}
</
style
>
frontend/src/business/components/track/plan/components/TestPlanTestCaseList.vue
浏览文件 @
b2d0e0cf
...
...
@@ -5,13 +5,18 @@
<template
v-slot:header
>
<div>
<el-row
type=
"flex"
justify=
"space-between"
align=
"middle"
>
<span
class=
"title"
>
{{
$t
(
'
test_track.case.test_case
'
)
}}
<span
class=
"title"
>
<node-breadcrumb
:node-names=
"selectNodeNames"
@
refresh=
"refresh"
/>
<ms-tip-button
v-if=
"!showMyTestCase"
:tip=
"$t('test_track.plan_view.my_case')"
icon=
"el-icon-s-custom"
@
click=
"searchMyTestCase"
/>
<ms-tip-button
v-if=
"showMyTestCase"
:tip=
"$t('test_track.plan_view.all_case')"
icon=
"el-icon-files"
@
click=
"searchMyTestCase"
/></span>
icon=
"el-icon-files"
@
click=
"searchMyTestCase"
/>
</span>
<span
class=
"operate-button"
>
<el-button
icon=
"el-icon-connection"
size=
"small"
round
...
...
@@ -23,23 +28,12 @@
<el-button
icon=
"el-icon-user"
size=
"small"
round
@
click=
"handleBatch('executor')"
>
{{
$t
(
'
test_track.plan_view.change_executor
'
)
}}
</el-button>
<el-input
type=
"text"
size=
"small"
class=
"search"
:placeholder=
"$t('load_test.search_by_name')"
prefix-icon=
"el-icon-search"
maxlength=
"60"
v-model=
"condition.name"
@
change=
"search"
clearable
/>
<ms-table-search-bar
:condition.sync=
"condition"
@
change=
"initTableData"
/>
</span>
</el-row>
<executor-edit
ref=
"executorEdit"
:select-ids=
"selectIds"
@
refresh=
"initTableData"
/>
<status-edit
ref=
"statusEdit"
:select-ids=
"selectIds"
@
refresh=
"initTableData"
/>
<executor-edit
ref=
"executorEdit"
:select-ids=
"selectIds"
@
refresh=
"refresh"
/>
<status-edit
ref=
"statusEdit"
:select-ids=
"selectIds"
@
refresh=
"refresh"
/>
</div>
</
template
>
...
...
@@ -154,25 +148,29 @@
<test-plan-test-case-edit
ref=
"testPlanTestCaseEdit"
:
table-data=
"tableData
"
@
refresh=
"
initTableData
"
/>
:
search-param=
"condition
"
@
refresh=
"
refresh
"
/>
</el-card>
</div>
</template>
<
script
>
import
PlanNodeTree
from
'
./PlanNodeTree
'
;
import
PlanNodeTree
from
'
.
./../common
/PlanNodeTree
'
;
import
ExecutorEdit
from
'
./ExecutorEdit
'
;
import
StatusEdit
from
'
./StatusEdit
'
;
import
TestPlanTestCaseEdit
from
"
../components/TestPlanTestCaseEdit
"
;
import
MsTipButton
from
'
../../../../components/common/components/MsTipButton
'
;
import
MsTablePagination
from
'
../../../../components/common/pagination/TablePagination
'
;
import
MsTableSearchBar
from
'
../../../../components/common/components/MsTableSearchBar
'
;
import
NodeBreadcrumb
from
'
../../common/NodeBreadcrumb
'
;
import
{
TokenKey
}
from
'
../../../../../common/js/constants
'
;
export
default
{
name
:
"
TestPlanTestCaseList
"
,
components
:
{
PlanNodeTree
,
StatusEdit
,
ExecutorEdit
,
MsTipButton
,
MsTablePagination
,
TestPlanTestCaseEdit
},
components
:
{
PlanNodeTree
,
StatusEdit
,
ExecutorEdit
,
MsTipButton
,
MsTablePagination
,
TestPlanTestCaseEdit
,
MsTableSearchBar
,
NodeBreadcrumb
},
data
()
{
return
{
result
:
{},
...
...
@@ -183,37 +181,47 @@
currentPage
:
1
,
pageSize
:
5
,
total
:
0
,
currentDataIndex
:
0
,
selectIds
:
new
Set
(),
selectIds
:
new
Set
()
}
},
props
:{
planId
:
{
type
:
String
},
selectNodeIds
:
{
type
:
Array
},
selectNodeNames
:
{
type
:
Array
}
},
watch
:
{
planId
()
{
this
.
initTableData
();
},
selectNodeIds
()
{
this
.
search
();
}
},
created
:
function
()
{
this
.
initTableData
();
},
methods
:
{
initTableData
(
nodeIds
)
{
initTableData
()
{
if
(
this
.
planId
)
{
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
.
condition
.
planId
=
this
.
planId
;
this
.
condition
.
nodeIds
=
this
.
selectNodeIds
;
this
.
result
=
this
.
$post
(
this
.
buildPagePath
(
'
/test/plan/case/list
'
),
this
.
condition
,
response
=>
{
let
data
=
response
.
data
;
this
.
total
=
data
.
itemCount
;
this
.
tableData
=
data
.
listObject
;
});
}
},
refresh
()
{
this
.
condition
=
{};
this
.
$emit
(
'
refresh
'
);
},
search
()
{
this
.
initTableData
();
},
...
...
@@ -221,10 +229,7 @@
return
path
+
"
/
"
+
this
.
currentPage
+
"
/
"
+
this
.
pageSize
;
},
handleEdit
(
testCase
,
index
)
{
this
.
currentDataIndex
=
index
;
this
.
$refs
.
testPlanTestCaseEdit
.
index
=
index
;
this
.
$refs
.
testPlanTestCaseEdit
.
getTestCase
(
index
);
this
.
$refs
.
testPlanTestCaseEdit
.
showDialog
=
true
;
this
.
$refs
.
testPlanTestCaseEdit
.
openTestCaseEdit
(
testCase
);
},
handleDelete
(
testCase
)
{
this
.
$alert
(
this
.
$t
(
'
test_track.plan_view.confirm_cancel_relevance
'
)
+
'
'
+
testCase
.
name
+
"
?
"
,
''
,
{
...
...
@@ -239,7 +244,6 @@
_handleDelete
(
testCase
)
{
let
testCaseId
=
testCase
.
id
;
this
.
$post
(
'
/test/plan/case/delete/
'
+
testCaseId
,
{},
()
=>
{
this
.
initTableData
();
this
.
$emit
(
"
refresh
"
);
this
.
$message
({
message
:
this
.
$t
(
'
commons.delete_success
'
),
...
...
@@ -299,5 +303,8 @@
float
:
right
;
}
.el-breadcrumb
{
display
:
inline-block
;
}
</
style
>
frontend/src/i18n/en-US.js
浏览文件 @
b2d0e0cf
...
...
@@ -46,7 +46,8 @@ export default {
'
refresh
'
:
'
Refresh
'
,
'
remark
'
:
'
Remark
'
,
'
delete
'
:
'
Delete
'
,
'
not_filled
'
:
'
Not filled
'
'
not_filled
'
:
'
Not filled
'
,
'
search_by_name
'
:
'
Search by name
'
,
},
workspace
:
{
'
create
'
:
'
Create Workspace
'
,
...
...
frontend/src/i18n/zh-CN.js
浏览文件 @
b2d0e0cf
...
...
@@ -48,6 +48,7 @@ export default {
'
delete
'
:
'
删除
'
,
'
not_filled
'
:
'
未填写
'
,
'
please_select
'
:
'
请选择
'
,
'
search_by_name
'
:
'
根据名称搜索
'
,
},
workspace
:
{
'
create
'
:
'
创建工作空间
'
,
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录