Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
MeterSphere
metersphere
提交
1aab595a
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,发现更多精彩内容 >>
提交
1aab595a
编写于
12月 07, 2020
作者:
C
chenjianxing
浏览文件
操作
浏览文件
下载
差异文件
Merge branch 'master' of
https://github.com/metersphere/metersphere
上级
7ba3c09a
8d7ec9b8
变更
74
隐藏空白更改
内联
并排
Showing
74 changed file
with
2912 addition
and
218 deletion
+2912
-218
backend/src/main/java/io/metersphere/api/controller/APIScenarioReportController.java
...tersphere/api/controller/APIScenarioReportController.java
+27
-0
backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java
...o/metersphere/api/controller/ApiAutomationController.java
+1
-2
backend/src/main/java/io/metersphere/api/controller/ApiMonitorController.java
...a/io/metersphere/api/controller/ApiMonitorController.java
+55
-0
backend/src/main/java/io/metersphere/api/dto/ApiMonitorSearch.java
...rc/main/java/io/metersphere/api/dto/ApiMonitorSearch.java
+10
-0
backend/src/main/java/io/metersphere/api/dto/ApiResponseCodeMonitor.java
...n/java/io/metersphere/api/dto/ApiResponseCodeMonitor.java
+20
-0
backend/src/main/java/io/metersphere/api/dto/ApiResponseTimeMonitor.java
...n/java/io/metersphere/api/dto/ApiResponseTimeMonitor.java
+20
-0
backend/src/main/java/io/metersphere/api/dto/automation/ScenarioStatus.java
...ava/io/metersphere/api/dto/automation/ScenarioStatus.java
+1
-1
backend/src/main/java/io/metersphere/api/dto/definition/RunDefinitionRequest.java
.../metersphere/api/dto/definition/RunDefinitionRequest.java
+2
-0
backend/src/main/java/io/metersphere/api/dto/definition/request/MsScenario.java
...io/metersphere/api/dto/definition/request/MsScenario.java
+25
-8
backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java
...metersphere/api/dto/definition/request/MsTestElement.java
+11
-3
backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestPlan.java
...io/metersphere/api/dto/definition/request/MsTestPlan.java
+3
-2
backend/src/main/java/io/metersphere/api/dto/definition/request/MsThreadGroup.java
...metersphere/api/dto/definition/request/MsThreadGroup.java
+4
-3
backend/src/main/java/io/metersphere/api/dto/definition/request/assertions/MsAssertions.java
...e/api/dto/definition/request/assertions/MsAssertions.java
+3
-2
backend/src/main/java/io/metersphere/api/dto/definition/request/auth/MsAuthManager.java
...sphere/api/dto/definition/request/auth/MsAuthManager.java
+2
-2
backend/src/main/java/io/metersphere/api/dto/definition/request/configurations/MsHeaderManager.java
...to/definition/request/configurations/MsHeaderManager.java
+3
-2
backend/src/main/java/io/metersphere/api/dto/definition/request/controller/MsIfController.java
...api/dto/definition/request/controller/MsIfController.java
+3
-2
backend/src/main/java/io/metersphere/api/dto/definition/request/dns/MsDNSCacheManager.java
...ere/api/dto/definition/request/dns/MsDNSCacheManager.java
+2
-2
backend/src/main/java/io/metersphere/api/dto/definition/request/extract/MsExtract.java
...rsphere/api/dto/definition/request/extract/MsExtract.java
+2
-1
backend/src/main/java/io/metersphere/api/dto/definition/request/processors/MsJSR223Processor.java
.../dto/definition/request/processors/MsJSR223Processor.java
+4
-3
backend/src/main/java/io/metersphere/api/dto/definition/request/processors/post/MsJSR223PostProcessor.java
...nition/request/processors/post/MsJSR223PostProcessor.java
+3
-2
backend/src/main/java/io/metersphere/api/dto/definition/request/processors/pre/MsJSR223PreProcessor.java
...finition/request/processors/pre/MsJSR223PreProcessor.java
+3
-2
backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsDubboSampler.java
...re/api/dto/definition/request/sampler/MsDubboSampler.java
+3
-2
backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java
...pi/dto/definition/request/sampler/MsHTTPSamplerProxy.java
+11
-10
backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsJDBCSampler.java
...ere/api/dto/definition/request/sampler/MsJDBCSampler.java
+3
-2
backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsTCPSampler.java
...here/api/dto/definition/request/sampler/MsTCPSampler.java
+3
-2
backend/src/main/java/io/metersphere/api/dto/definition/request/timer/MsConstantTimer.java
...ere/api/dto/definition/request/timer/MsConstantTimer.java
+3
-2
backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java
...a/io/metersphere/api/jmeter/APIBackendListenerClient.java
+3
-7
backend/src/main/java/io/metersphere/api/service/APIMonitorService.java
...in/java/io/metersphere/api/service/APIMonitorService.java
+35
-0
backend/src/main/java/io/metersphere/api/service/APIReportService.java
...ain/java/io/metersphere/api/service/APIReportService.java
+51
-0
backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java
...java/io/metersphere/api/service/ApiAutomationService.java
+39
-9
backend/src/main/java/io/metersphere/api/service/ApiScenarioReportService.java
.../io/metersphere/api/service/ApiScenarioReportService.java
+48
-5
backend/src/main/java/io/metersphere/base/domain/ApiDataView.java
...src/main/java/io/metersphere/base/domain/ApiDataView.java
+24
-0
backend/src/main/java/io/metersphere/base/mapper/ApiDataViewMapper.java
...in/java/io/metersphere/base/mapper/ApiDataViewMapper.java
+23
-0
backend/src/main/java/io/metersphere/base/mapper/ApiDataViewMapper.xml
...ain/java/io/metersphere/base/mapper/ApiDataViewMapper.xml
+77
-0
backend/src/main/resources/db/migration/V47__api_data_view.sql
...nd/src/main/resources/db/migration/V47__api_data_view.sql
+12
-0
frontend/src/business/components/api/automation/ApiAutomation.vue
.../src/business/components/api/automation/ApiAutomation.vue
+72
-67
frontend/src/business/components/api/automation/report/ApiReport.vue
...c/business/components/api/automation/report/ApiReport.vue
+0
-21
frontend/src/business/components/api/automation/report/ApiReportDetail.vue
...ness/components/api/automation/report/ApiReportDetail.vue
+220
-0
frontend/src/business/components/api/automation/report/ApiReportExport.vue
...ness/components/api/automation/report/ApiReportExport.vue
+131
-0
frontend/src/business/components/api/automation/report/ApiReportReqestHeaderItem.vue
...nents/api/automation/report/ApiReportReqestHeaderItem.vue
+31
-0
frontend/src/business/components/api/automation/report/ApiReportStatus.vue
...ness/components/api/automation/report/ApiReportStatus.vue
+41
-0
frontend/src/business/components/api/automation/report/ApiReportViewHeader.vue
.../components/api/automation/report/ApiReportViewHeader.vue
+52
-0
frontend/src/business/components/api/automation/report/components/AssertionResults.vue
...nts/api/automation/report/components/AssertionResults.vue
+36
-0
frontend/src/business/components/api/automation/report/components/MetricChart.vue
...mponents/api/automation/report/components/MetricChart.vue
+255
-0
frontend/src/business/components/api/automation/report/components/RequestMetric.vue
...onents/api/automation/report/components/RequestMetric.vue
+106
-0
frontend/src/business/components/api/automation/report/components/RequestResult.vue
...onents/api/automation/report/components/RequestResult.vue
+144
-0
frontend/src/business/components/api/automation/report/components/RequestResultTail.vue
...ts/api/automation/report/components/RequestResultTail.vue
+195
-0
frontend/src/business/components/api/automation/report/components/RequestText.vue
...mponents/api/automation/report/components/RequestText.vue
+73
-0
frontend/src/business/components/api/automation/report/components/ResponseText.vue
...ponents/api/automation/report/components/ResponseText.vue
+135
-0
frontend/src/business/components/api/automation/report/components/ScenarioResult.vue
...nents/api/automation/report/components/ScenarioResult.vue
+71
-0
frontend/src/business/components/api/automation/report/components/ScenarioResults.vue
...ents/api/automation/report/components/ScenarioResults.vue
+35
-0
frontend/src/business/components/api/automation/report/components/SqlResultTable.vue
...nents/api/automation/report/components/SqlResultTable.vue
+121
-0
frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue
...ss/components/api/automation/scenario/ApiScenarioList.vue
+5
-3
frontend/src/business/components/api/automation/scenario/EditApiScenario.vue
...ss/components/api/automation/scenario/EditApiScenario.vue
+34
-21
frontend/src/business/components/api/automation/scenario/ImportApiScenario.vue
.../components/api/automation/scenario/ImportApiScenario.vue
+1
-0
frontend/src/business/components/api/automation/scenario/Run.vue
...d/src/business/components/api/automation/scenario/Run.vue
+9
-14
frontend/src/business/components/api/definition/components/jmeter/components/test-plan/index.js
...efinition/components/jmeter/components/test-plan/index.js
+1
-1
frontend/src/business/components/api/head/ApiHeaderMenus.vue
frontend/src/business/components/api/head/ApiHeaderMenus.vue
+5
-0
frontend/src/business/components/api/monitor/ApiMonitor.vue
frontend/src/business/components/api/monitor/ApiMonitor.vue
+168
-0
frontend/src/business/components/api/monitor/ApiMonitorChart.vue
...d/src/business/components/api/monitor/ApiMonitorChart.vue
+46
-0
frontend/src/business/components/api/monitor/ApiMonitorSearch.vue
.../src/business/components/api/monitor/ApiMonitorSearch.vue
+100
-0
frontend/src/business/components/api/monitor/components/ApiErrorMonitorChart.vue
...omponents/api/monitor/components/ApiErrorMonitorChart.vue
+93
-0
frontend/src/business/components/api/monitor/components/ApiResponseTimeMonitorChart.vue
...ts/api/monitor/components/ApiResponseTimeMonitorChart.vue
+64
-0
frontend/src/business/components/api/monitor/components/CommonMonitorChart.vue
.../components/api/monitor/components/CommonMonitorChart.vue
+33
-0
frontend/src/business/components/api/report/components/ResponseText.vue
...usiness/components/api/report/components/ResponseText.vue
+1
-1
frontend/src/business/components/api/report/components/SqlResultTable.vue
...iness/components/api/report/components/SqlResultTable.vue
+8
-0
frontend/src/business/components/performance/report/components/PerformancePressureConfig.vue
...rformance/report/components/PerformancePressureConfig.vue
+5
-4
frontend/src/business/components/performance/test/components/PerformancePressureConfig.vue
...performance/test/components/PerformancePressureConfig.vue
+5
-4
frontend/src/common/js/ajax.js
frontend/src/common/js/ajax.js
+3
-3
frontend/src/common/js/chart.js
frontend/src/common/js/chart.js
+2
-0
frontend/src/common/js/format-utils.js
frontend/src/common/js/format-utils.js
+36
-0
frontend/src/i18n/en-US.js
frontend/src/i18n/en-US.js
+12
-1
frontend/src/i18n/zh-CN.js
frontend/src/i18n/zh-CN.js
+12
-1
frontend/src/i18n/zh-TW.js
frontend/src/i18n/zh-TW.js
+12
-1
未找到文件。
backend/src/main/java/io/metersphere/api/controller/APIScenarioReportController.java
0 → 100644
浏览文件 @
1aab595a
package
io.metersphere.api.controller
;
import
io.metersphere.api.dto.APIReportResult
;
import
io.metersphere.api.service.ApiScenarioReportService
;
import
io.metersphere.commons.constants.RoleConstants
;
import
org.apache.shiro.authz.annotation.Logical
;
import
org.apache.shiro.authz.annotation.RequiresRoles
;
import
org.springframework.web.bind.annotation.GetMapping
;
import
org.springframework.web.bind.annotation.PathVariable
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
import
javax.annotation.Resource
;
@RestController
@RequestMapping
(
value
=
"/api/scenario/report"
)
@RequiresRoles
(
value
=
{
RoleConstants
.
TEST_MANAGER
,
RoleConstants
.
TEST_USER
,
RoleConstants
.
TEST_VIEWER
},
logical
=
Logical
.
OR
)
public
class
APIScenarioReportController
{
@Resource
private
ApiScenarioReportService
apiReportService
;
@GetMapping
(
"/get/{reportId}"
)
public
APIReportResult
get
(
@PathVariable
String
reportId
)
{
return
apiReportService
.
getCacheResult
(
reportId
);
}
}
backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java
浏览文件 @
1aab595a
...
...
@@ -70,10 +70,9 @@ public class ApiAutomationController {
return
apiAutomationService
.
getApiScenarios
(
ids
);
}
@PostMapping
(
value
=
"/run
/debug
"
)
@PostMapping
(
value
=
"/run"
)
public
void
runDebug
(
@RequestPart
(
"request"
)
RunDefinitionRequest
request
,
@RequestPart
(
value
=
"files"
)
List
<
MultipartFile
>
bodyFiles
)
{
apiAutomationService
.
run
(
request
,
bodyFiles
);
}
}
backend/src/main/java/io/metersphere/api/controller/ApiMonitorController.java
0 → 100644
浏览文件 @
1aab595a
package
io.metersphere.api.controller
;
import
io.metersphere.api.dto.ApiMonitorSearch
;
import
io.metersphere.api.dto.ApiResponseCodeMonitor
;
import
io.metersphere.api.dto.ApiResponseTimeMonitor
;
import
io.metersphere.api.service.APIMonitorService
;
import
io.metersphere.commons.constants.RoleConstants
;
import
org.apache.shiro.authz.annotation.Logical
;
import
org.apache.shiro.authz.annotation.RequiresRoles
;
import
org.springframework.web.bind.annotation.*
;
import
javax.annotation.Resource
;
import
java.util.List
;
@RestController
@RequestMapping
(
value
=
"/api/monitor"
)
@RequiresRoles
(
value
=
{
RoleConstants
.
TEST_MANAGER
,
RoleConstants
.
TEST_USER
,
RoleConstants
.
TEST_VIEWER
},
logical
=
Logical
.
OR
)
public
class
ApiMonitorController
{
@Resource
private
APIMonitorService
apiMonitorService
;
/**
* 查询所有接口
*/
@GetMapping
(
"/list"
)
public
List
<
ApiMonitorSearch
>
apiList
()
{
return
apiMonitorService
.
list
();
}
/**
* 查询响应时间
*/
@GetMapping
(
"/getResponseTime"
)
public
List
<
ApiResponseTimeMonitor
>
responseTimeData
(
@RequestHeader
(
"apiUrl"
)
String
url
,
String
startTime
,
String
endTime
)
{
return
apiMonitorService
.
getApiResponseTimeData
(
url
,
startTime
,
endTime
);
}
/**
* 查询状态码
*/
@GetMapping
(
"/getResponseCode"
)
public
List
<
ApiResponseCodeMonitor
>
responseCodeData
(
@RequestHeader
(
"apiUrl"
)
String
url
,
String
startTime
,
String
endTime
)
{
return
apiMonitorService
.
getApiResponseCodeData
(
url
,
startTime
,
endTime
);
}
/**
* 查询reportId
*/
@GetMapping
(
"/getReportId"
)
public
String
searchReportId
(
@RequestHeader
(
"apiUrl"
)
String
url
,
@RequestParam
(
"startTime"
)
String
startTime
)
{
return
apiMonitorService
.
getReportId
(
url
,
startTime
);
}
}
backend/src/main/java/io/metersphere/api/dto/ApiMonitorSearch.java
0 → 100644
浏览文件 @
1aab595a
package
io.metersphere.api.dto
;
import
lombok.Data
;
@Data
public
class
ApiMonitorSearch
{
private
String
url
;
}
\ No newline at end of file
backend/src/main/java/io/metersphere/api/dto/ApiResponseCodeMonitor.java
0 → 100644
浏览文件 @
1aab595a
package
io.metersphere.api.dto
;
import
lombok.Data
;
@Data
public
class
ApiResponseCodeMonitor
{
private
String
id
;
private
String
reportId
;
private
String
url
;
private
String
apiName
;
private
String
startTime
;
private
String
responseCode
;
}
backend/src/main/java/io/metersphere/api/dto/ApiResponseTimeMonitor.java
0 → 100644
浏览文件 @
1aab595a
package
io.metersphere.api.dto
;
import
lombok.Data
;
@Data
public
class
ApiResponseTimeMonitor
{
private
String
id
;
private
String
reportId
;
private
String
url
;
private
String
apiName
;
private
String
startTime
;
private
String
responseTime
;
}
backend/src/main/java/io/metersphere/api/dto/automation/ScenarioStatus.java
浏览文件 @
1aab595a
package
io.metersphere.api.dto.automation
;
public
enum
ScenarioStatus
{
Saved
,
Success
,
Fail
,
Trash
Saved
,
Success
,
Fail
,
Trash
,
Underway
}
backend/src/main/java/io/metersphere/api/dto/definition/RunDefinitionRequest.java
浏览文件 @
1aab595a
...
...
@@ -15,6 +15,8 @@ public class RunDefinitionRequest {
private
String
reportId
;
private
String
environmentId
;
private
MsTestElement
testElement
;
private
Response
response
;
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/MsScenario.java
浏览文件 @
1aab595a
package
io.metersphere.api.dto.definition.request
;
import
com.alibaba.fastjson.JSON
;
import
com.alibaba.fastjson.JSONObject
;
import
com.alibaba.fastjson.annotation.JSONField
;
import
com.alibaba.fastjson.annotation.JSONType
;
import
com.google.gson.Gson
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
io.metersphere.api.service.ApiAutomationService
;
import
io.metersphere.api.service.ApiTestEnvironmentService
;
import
io.metersphere.base.domain.ApiScenario
;
import
io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs
;
import
io.metersphere.commons.utils.CommonBeanFactory
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
...
...
@@ -21,23 +25,36 @@ public class MsScenario extends MsTestElement {
private
String
type
=
"scenario"
;
@JSONField
(
ordinal
=
10
)
private
String
name
;
@JSONField
(
ordinal
=
11
)
private
String
referenced
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
@JSONField
(
ordinal
=
12
)
private
String
environmentId
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
if
(
environmentId
!=
null
)
{
ApiTestEnvironmentService
environmentService
=
CommonBeanFactory
.
getBean
(
ApiTestEnvironmentService
.
class
);
ApiTestEnvironmentWithBLOBs
environment
=
environmentService
.
get
(
environmentId
);
config
=
JSONObject
.
parseObject
(
environment
.
getConfig
(),
EnvironmentConfig
.
class
);
}
if
(
this
.
getReferenced
()
!=
null
&&
this
.
getReferenced
().
equals
(
"Deleted"
))
{
return
;
}
else
if
(
this
.
getReferenced
()
!=
null
&&
this
.
getReferenced
().
equals
(
"REF"
))
{
ApiAutomationService
apiAutomationService
=
CommonBeanFactory
.
getBean
(
ApiAutomationService
.
class
);
ApiScenario
scenario
=
apiAutomationService
.
getApiScenario
(
this
.
getId
());
Gson
gs
=
new
Gson
();
MsTestElement
element
=
gs
.
fromJson
(
scenario
.
getScenarioDefinition
(),
MsTestElement
.
class
);
hashTree
.
add
(
element
);
JSONObject
element
=
JSON
.
parseObject
(
scenario
.
getScenarioDefinition
());
List
<
MsTestElement
>
dataArr
=
JSON
.
parseArray
(
element
.
getString
(
"hashTree"
),
MsTestElement
.
class
);
if
(
hashTree
==
null
)
{
hashTree
=
dataArr
;
}
else
{
hashTree
.
addAll
(
dataArr
);
}
}
if
(
CollectionUtils
.
isNotEmpty
(
hashTree
))
{
hashTree
.
forEach
(
el
->
{
el
.
toHashTree
(
tree
,
el
.
getHashTree
());
}
);
for
(
MsTestElement
el
:
hashTree
)
{
el
.
toHashTree
(
tree
,
el
.
getHashTree
()
,
config
);
}
}
}
}
backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java
浏览文件 @
1aab595a
...
...
@@ -16,6 +16,7 @@ import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
import
io.metersphere.api.dto.definition.request.sampler.MsJDBCSampler
;
import
io.metersphere.api.dto.definition.request.sampler.MsTCPSampler
;
import
io.metersphere.api.dto.definition.request.timer.MsConstantTimer
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
io.metersphere.commons.utils.LogUtil
;
import
lombok.Data
;
import
org.apache.jmeter.protocol.http.control.AuthManager
;
...
...
@@ -61,9 +62,10 @@ public abstract class MsTestElement {
@JSONField
(
ordinal
=
4
)
private
LinkedList
<
MsTestElement
>
hashTree
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
// 公共环境逐层传递,如果自身有环境 以自身引用环境为准否则以公共环境作为请求环境
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
for
(
MsTestElement
el
:
hashTree
)
{
el
.
toHashTree
(
tree
,
el
.
hashTree
);
el
.
toHashTree
(
tree
,
el
.
hashTree
,
config
);
}
}
...
...
@@ -85,9 +87,15 @@ public abstract class MsTestElement {
return
null
;
}
public
HashTree
generateHashTree
(
EnvironmentConfig
config
)
{
HashTree
jmeterTestPlanHashTree
=
new
ListedHashTree
();
this
.
toHashTree
(
jmeterTestPlanHashTree
,
this
.
hashTree
,
config
);
return
jmeterTestPlanHashTree
;
}
public
HashTree
generateHashTree
()
{
HashTree
jmeterTestPlanHashTree
=
new
ListedHashTree
();
this
.
toHashTree
(
jmeterTestPlanHashTree
,
this
.
hashTree
);
this
.
toHashTree
(
jmeterTestPlanHashTree
,
this
.
hashTree
,
null
);
return
jmeterTestPlanHashTree
;
}
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestPlan.java
浏览文件 @
1aab595a
package
io.metersphere.api.dto.definition.request
;
import
com.alibaba.fastjson.annotation.JSONType
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
org.apache.commons.collections.CollectionUtils
;
...
...
@@ -18,11 +19,11 @@ import java.util.List;
public
class
MsTestPlan
extends
MsTestElement
{
private
String
type
=
"TestPlan"
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
final
HashTree
testPlanTree
=
tree
.
add
(
getPlan
());
if
(
CollectionUtils
.
isNotEmpty
(
hashTree
))
{
hashTree
.
forEach
(
el
->
{
el
.
toHashTree
(
testPlanTree
,
el
.
getHashTree
());
el
.
toHashTree
(
testPlanTree
,
el
.
getHashTree
()
,
config
);
});
}
}
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/MsThreadGroup.java
浏览文件 @
1aab595a
package
io.metersphere.api.dto.definition.request
;
import
com.alibaba.fastjson.annotation.JSONType
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
org.apache.commons.collections.CollectionUtils
;
...
...
@@ -18,11 +19,11 @@ import java.util.List;
public
class
MsThreadGroup
extends
MsTestElement
{
private
String
type
=
"ThreadGroup"
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
final
HashTree
groupTree
=
tree
.
add
(
getThreadGroup
());
if
(
CollectionUtils
.
isNotEmpty
(
hashTree
))
{
hashTree
.
forEach
(
el
->
{
el
.
toHashTree
(
groupTree
,
el
.
getHashTree
());
el
.
toHashTree
(
groupTree
,
el
.
getHashTree
()
,
config
);
});
}
}
...
...
@@ -37,7 +38,7 @@ public class MsThreadGroup extends MsTestElement {
ThreadGroup
threadGroup
=
new
ThreadGroup
();
threadGroup
.
setEnabled
(
true
);
threadGroup
.
setName
(
this
.
getName
()
+
"ThreadGroup"
);
threadGroup
.
setName
(
this
.
getName
());
threadGroup
.
setProperty
(
TestElement
.
TEST_CLASS
,
ThreadGroup
.
class
.
getName
());
threadGroup
.
setProperty
(
TestElement
.
GUI_CLASS
,
SaveService
.
aliasToClass
(
"ThreadGroupGui"
));
threadGroup
.
setNumThreads
(
1
);
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/assertions/MsAssertions.java
浏览文件 @
1aab595a
...
...
@@ -2,6 +2,7 @@ package io.metersphere.api.dto.definition.request.assertions;
import
com.alibaba.fastjson.annotation.JSONType
;
import
io.metersphere.api.dto.definition.request.MsTestElement
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
org.apache.commons.collections.CollectionUtils
;
...
...
@@ -23,10 +24,10 @@ public class MsAssertions extends MsTestElement {
private
MsAssertionDuration
duration
;
private
String
type
=
"Assertions"
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
addAssertions
(
tree
);
}
private
void
addAssertions
(
HashTree
hashTree
)
{
if
(
CollectionUtils
.
isNotEmpty
(
this
.
getRegex
()))
{
this
.
getRegex
().
stream
().
filter
(
MsAssertionRegex:
:
isValid
).
forEach
(
assertion
->
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/auth/MsAuthManager.java
浏览文件 @
1aab595a
...
...
@@ -50,7 +50,7 @@ public class MsAuthManager extends MsTestElement {
@JSONField
(
ordinal
=
18
)
private
String
environment
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
AuthManager
authManager
=
new
AuthManager
();
authManager
.
setEnabled
(
true
);
authManager
.
setName
(
this
.
getUsername
()
+
"AuthManager"
);
...
...
@@ -63,7 +63,7 @@ public class MsAuthManager extends MsTestElement {
if
(
environment
!=
null
)
{
ApiTestEnvironmentService
environmentService
=
CommonBeanFactory
.
getBean
(
ApiTestEnvironmentService
.
class
);
ApiTestEnvironmentWithBLOBs
environmentWithBLOBs
=
environmentService
.
get
(
environment
);
EnvironmentConfig
config
=
JSONObject
.
parseObject
(
environmentWithBLOBs
.
getConfig
(),
EnvironmentConfig
.
class
);
config
=
JSONObject
.
parseObject
(
environmentWithBLOBs
.
getConfig
(),
EnvironmentConfig
.
class
);
this
.
url
=
config
.
getHttpConfig
().
getProtocol
()
+
"://"
+
config
.
getHttpConfig
().
getSocket
();
}
}
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/configurations/MsHeaderManager.java
浏览文件 @
1aab595a
...
...
@@ -4,6 +4,7 @@ import com.alibaba.fastjson.annotation.JSONField;
import
com.alibaba.fastjson.annotation.JSONType
;
import
io.metersphere.api.dto.definition.request.MsTestElement
;
import
io.metersphere.api.dto.scenario.KeyValue
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
org.apache.commons.collections.CollectionUtils
;
...
...
@@ -24,7 +25,7 @@ public class MsHeaderManager extends MsTestElement {
@JSONField
(
ordinal
=
10
)
private
List
<
KeyValue
>
headers
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
HeaderManager
headerManager
=
new
HeaderManager
();
headerManager
.
setEnabled
(
true
);
headerManager
.
setName
(
this
.
getName
()
+
"Headers"
);
...
...
@@ -36,7 +37,7 @@ public class MsHeaderManager extends MsTestElement {
final
HashTree
headersTree
=
tree
.
add
(
headerManager
);
if
(
CollectionUtils
.
isNotEmpty
(
hashTree
))
{
hashTree
.
forEach
(
el
->
{
el
.
toHashTree
(
headersTree
,
el
.
getHashTree
());
el
.
toHashTree
(
headersTree
,
el
.
getHashTree
()
,
config
);
});
}
}
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/controller/MsIfController.java
浏览文件 @
1aab595a
...
...
@@ -2,6 +2,7 @@ package io.metersphere.api.dto.definition.request.controller;
import
com.alibaba.fastjson.annotation.JSONType
;
import
io.metersphere.api.dto.definition.request.MsTestElement
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
org.apache.commons.collections.CollectionUtils
;
...
...
@@ -24,11 +25,11 @@ public class MsIfController extends MsTestElement {
private
String
operator
;
private
String
value
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
final
HashTree
groupTree
=
tree
.
add
(
ifController
());
if
(
CollectionUtils
.
isNotEmpty
(
hashTree
))
{
hashTree
.
forEach
(
el
->
{
el
.
toHashTree
(
groupTree
,
el
.
getHashTree
());
el
.
toHashTree
(
groupTree
,
el
.
getHashTree
()
,
config
);
});
}
}
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/dns/MsDNSCacheManager.java
浏览文件 @
1aab595a
...
...
@@ -23,9 +23,9 @@ import java.util.List;
@JSONType
(
typeName
=
"DNSCacheManager"
)
public
class
MsDNSCacheManager
extends
MsTestElement
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
for
(
MsTestElement
el
:
hashTree
)
{
el
.
toHashTree
(
tree
,
el
.
getHashTree
());
el
.
toHashTree
(
tree
,
el
.
getHashTree
()
,
config
);
}
}
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/extract/MsExtract.java
浏览文件 @
1aab595a
...
...
@@ -2,6 +2,7 @@ package io.metersphere.api.dto.definition.request.extract;
import
com.alibaba.fastjson.annotation.JSONType
;
import
io.metersphere.api.dto.definition.request.MsTestElement
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
org.apache.commons.collections.CollectionUtils
;
...
...
@@ -23,7 +24,7 @@ public class MsExtract extends MsTestElement {
private
List
<
MsExtractXPath
>
xpath
;
private
String
type
=
"Extract"
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
addRequestExtractors
(
tree
);
}
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/processors/MsJSR223Processor.java
浏览文件 @
1aab595a
...
...
@@ -3,13 +3,14 @@ package io.metersphere.api.dto.definition.request.processors;
import
com.alibaba.fastjson.annotation.JSONField
;
import
com.alibaba.fastjson.annotation.JSONType
;
import
io.metersphere.api.dto.definition.request.MsTestElement
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
org.apache.commons.collections.CollectionUtils
;
import
org.apache.jmeter.protocol.java.sampler.JSR223Sampler
;
import
org.apache.jmeter.save.SaveService
;
import
org.apache.jmeter.testelement.TestElement
;
import
org.apache.jorphan.collections.HashTree
;
import
org.apache.jmeter.protocol.java.sampler.JSR223Sampler
;
import
java.util.List
;
...
...
@@ -25,7 +26,7 @@ public class MsJSR223Processor extends MsTestElement {
@JSONField
(
ordinal
=
11
)
private
String
scriptLanguage
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
JSR223Sampler
processor
=
new
JSR223Sampler
();
processor
.
setEnabled
(
true
);
processor
.
setName
(
this
.
getName
()
+
"JSR223Processor"
);
...
...
@@ -38,7 +39,7 @@ public class MsJSR223Processor extends MsTestElement {
final
HashTree
jsr223PreTree
=
tree
.
add
(
processor
);
if
(
CollectionUtils
.
isNotEmpty
(
hashTree
))
{
hashTree
.
forEach
(
el
->
{
el
.
toHashTree
(
jsr223PreTree
,
el
.
getHashTree
());
el
.
toHashTree
(
jsr223PreTree
,
el
.
getHashTree
()
,
config
);
});
}
}
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/processors/post/MsJSR223PostProcessor.java
浏览文件 @
1aab595a
...
...
@@ -3,6 +3,7 @@ package io.metersphere.api.dto.definition.request.processors.post;
import
com.alibaba.fastjson.annotation.JSONField
;
import
com.alibaba.fastjson.annotation.JSONType
;
import
io.metersphere.api.dto.definition.request.MsTestElement
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
org.apache.commons.collections.CollectionUtils
;
...
...
@@ -26,7 +27,7 @@ public class MsJSR223PostProcessor extends MsTestElement {
private
String
scriptLanguage
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
JSR223PostProcessor
processor
=
new
JSR223PostProcessor
();
processor
.
setEnabled
(
true
);
processor
.
setName
(
this
.
getName
()
+
"JSR223PostProcessor"
);
...
...
@@ -39,7 +40,7 @@ public class MsJSR223PostProcessor extends MsTestElement {
final
HashTree
jsr223PostTree
=
tree
.
add
(
processor
);
if
(
CollectionUtils
.
isNotEmpty
(
hashTree
))
{
hashTree
.
forEach
(
el
->
{
el
.
toHashTree
(
jsr223PostTree
,
el
.
getHashTree
());
el
.
toHashTree
(
jsr223PostTree
,
el
.
getHashTree
()
,
config
);
});
}
}
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/processors/pre/MsJSR223PreProcessor.java
浏览文件 @
1aab595a
...
...
@@ -3,6 +3,7 @@ package io.metersphere.api.dto.definition.request.processors.pre;
import
com.alibaba.fastjson.annotation.JSONField
;
import
com.alibaba.fastjson.annotation.JSONType
;
import
io.metersphere.api.dto.definition.request.MsTestElement
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
org.apache.commons.collections.CollectionUtils
;
...
...
@@ -25,7 +26,7 @@ public class MsJSR223PreProcessor extends MsTestElement {
@JSONField
(
ordinal
=
11
)
private
String
scriptLanguage
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
JSR223PreProcessor
processor
=
new
JSR223PreProcessor
();
processor
.
setEnabled
(
true
);
processor
.
setName
(
this
.
getName
()
+
"JSR223PreProcessor"
);
...
...
@@ -38,7 +39,7 @@ public class MsJSR223PreProcessor extends MsTestElement {
final
HashTree
jsr223PreTree
=
tree
.
add
(
processor
);
if
(
CollectionUtils
.
isNotEmpty
(
hashTree
))
{
hashTree
.
forEach
(
el
->
{
el
.
toHashTree
(
jsr223PreTree
,
el
.
getHashTree
());
el
.
toHashTree
(
jsr223PreTree
,
el
.
getHashTree
()
,
config
);
});
}
}
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsDubboSampler.java
浏览文件 @
1aab595a
...
...
@@ -11,6 +11,7 @@ import io.metersphere.api.dto.definition.request.sampler.dubbo.MsConfigCenter;
import
io.metersphere.api.dto.definition.request.sampler.dubbo.MsConsumerAndService
;
import
io.metersphere.api.dto.definition.request.sampler.dubbo.MsRegistryCenter
;
import
io.metersphere.api.dto.scenario.KeyValue
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
org.apache.commons.collections.CollectionUtils
;
...
...
@@ -50,13 +51,13 @@ public class MsDubboSampler extends MsTestElement {
@JSONField
(
ordinal
=
59
)
private
List
<
KeyValue
>
attachmentArgs
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
final
HashTree
testPlanTree
=
new
ListedHashTree
();
testPlanTree
.
add
(
dubboConfig
());
tree
.
set
(
dubboSample
(),
testPlanTree
);
if
(
CollectionUtils
.
isNotEmpty
(
hashTree
))
{
hashTree
.
forEach
(
el
->
{
el
.
toHashTree
(
testPlanTree
,
el
.
getHashTree
());
el
.
toHashTree
(
testPlanTree
,
el
.
getHashTree
()
,
config
);
});
}
}
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java
浏览文件 @
1aab595a
...
...
@@ -52,12 +52,12 @@ public class MsHTTPSamplerProxy extends MsTestElement {
@JSONField
(
ordinal
=
15
)
private
String
connectTimeout
;
@JSONField
(
ordinal
=
16
)
@JSONField
(
ordinal
=
16
)
private
String
responseTimeout
;
@JSONField
(
ordinal
=
17
)
private
List
<
KeyValue
>
arguments
;
@JSONField
(
ordinal
=
17
)
private
List
<
KeyValue
>
headers
;
@JSONField
(
ordinal
=
18
)
private
Body
body
;
...
...
@@ -78,9 +78,9 @@ public class MsHTTPSamplerProxy extends MsTestElement {
private
String
useEnvironment
;
@JSONField
(
ordinal
=
24
)
private
List
<
KeyValue
>
header
s
;
private
List
<
KeyValue
>
argument
s
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
HTTPSamplerProxy
sampler
=
new
HTTPSamplerProxy
();
sampler
.
setEnabled
(
true
);
sampler
.
setName
(
this
.
getName
());
...
...
@@ -93,7 +93,6 @@ public class MsHTTPSamplerProxy extends MsTestElement {
sampler
.
setFollowRedirects
(
this
.
isFollowRedirects
());
sampler
.
setUseKeepAlive
(
true
);
sampler
.
setDoMultipart
(
this
.
isDoMultipartPost
());
EnvironmentConfig
config
=
null
;
if
(
useEnvironment
!=
null
)
{
ApiTestEnvironmentService
environmentService
=
CommonBeanFactory
.
getBean
(
ApiTestEnvironmentService
.
class
);
ApiTestEnvironmentWithBLOBs
environment
=
environmentService
.
get
(
useEnvironment
);
...
...
@@ -145,16 +144,18 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
final
HashTree
httpSamplerTree
=
tree
.
add
(
sampler
);
setHeader
(
httpSamplerTree
);
if
(
CollectionUtils
.
isNotEmpty
(
this
.
headers
))
{
setHeader
(
httpSamplerTree
);
}
//判断是否要开启DNS
if
(
config
!=
null
&&
config
.
getCommonConfig
()
!=
null
&&
config
.
getCommonConfig
().
isEnableHost
())
{
MsDNSCacheManager
.
addEnvironmentVariables
(
httpSamplerTree
,
this
.
getName
(),
config
);
MsDNSCacheManager
.
addEnvironmentDNS
(
httpSamplerTree
,
this
.
getName
(),
config
);
}
if
(
CollectionUtils
.
isNotEmpty
(
hashTree
))
{
hashTree
.
forEach
(
el
->
{
el
.
toHashTree
(
httpSamplerTree
,
el
.
getHashTree
());
}
);
for
(
MsTestElement
el
:
hashTree
)
{
el
.
toHashTree
(
httpSamplerTree
,
el
.
getHashTree
()
,
config
);
}
}
}
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsJDBCSampler.java
浏览文件 @
1aab595a
...
...
@@ -5,6 +5,7 @@ import com.alibaba.fastjson.annotation.JSONType;
import
io.metersphere.api.dto.definition.request.MsTestElement
;
import
io.metersphere.api.dto.scenario.DatabaseConfig
;
import
io.metersphere.api.dto.scenario.KeyValue
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
org.apache.commons.collections.CollectionUtils
;
...
...
@@ -38,13 +39,13 @@ public class MsJDBCSampler extends MsTestElement {
@JSONField
(
ordinal
=
16
)
private
String
environmentId
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
final
HashTree
samplerHashTree
=
tree
.
add
(
jdbcSampler
());
tree
.
add
(
jdbcDataSource
());
tree
.
add
(
arguments
(
this
.
getName
()
+
" Variables"
,
this
.
getVariables
()));
if
(
CollectionUtils
.
isNotEmpty
(
hashTree
))
{
hashTree
.
forEach
(
el
->
{
el
.
toHashTree
(
samplerHashTree
,
el
.
getHashTree
());
el
.
toHashTree
(
samplerHashTree
,
el
.
getHashTree
()
,
config
);
});
}
}
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsTCPSampler.java
浏览文件 @
1aab595a
...
...
@@ -3,6 +3,7 @@ package io.metersphere.api.dto.definition.request.sampler;
import
com.alibaba.fastjson.annotation.JSONField
;
import
com.alibaba.fastjson.annotation.JSONType
;
import
io.metersphere.api.dto.definition.request.MsTestElement
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
org.apache.commons.collections.CollectionUtils
;
...
...
@@ -48,13 +49,13 @@ public class MsTCPSampler extends MsTestElement {
@JSONField
(
ordinal
=
23
)
private
String
request
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
final
HashTree
samplerHashTree
=
new
ListedHashTree
();
samplerHashTree
.
add
(
tcpConfig
());
tree
.
set
(
tcpSampler
(),
samplerHashTree
);
if
(
CollectionUtils
.
isNotEmpty
(
hashTree
))
{
hashTree
.
forEach
(
el
->
{
el
.
toHashTree
(
samplerHashTree
,
el
.
getHashTree
());
el
.
toHashTree
(
samplerHashTree
,
el
.
getHashTree
()
,
config
);
});
}
}
...
...
backend/src/main/java/io/metersphere/api/dto/definition/request/timer/MsConstantTimer.java
浏览文件 @
1aab595a
...
...
@@ -3,6 +3,7 @@ package io.metersphere.api.dto.definition.request.timer;
import
com.alibaba.fastjson.annotation.JSONField
;
import
com.alibaba.fastjson.annotation.JSONType
;
import
io.metersphere.api.dto.definition.request.MsTestElement
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
lombok.Data
;
import
lombok.EqualsAndHashCode
;
import
org.apache.commons.collections.CollectionUtils
;
...
...
@@ -25,11 +26,11 @@ public class MsConstantTimer extends MsTestElement {
@JSONField
(
ordinal
=
12
)
private
String
delay
;
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
)
{
public
void
toHashTree
(
HashTree
tree
,
List
<
MsTestElement
>
hashTree
,
EnvironmentConfig
config
)
{
final
HashTree
groupTree
=
tree
.
add
(
constantTimer
());
if
(
CollectionUtils
.
isNotEmpty
(
hashTree
))
{
hashTree
.
forEach
(
el
->
{
el
.
toHashTree
(
groupTree
,
el
.
getHashTree
());
el
.
toHashTree
(
groupTree
,
el
.
getHashTree
()
,
config
);
});
}
}
...
...
backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java
浏览文件 @
1aab595a
...
...
@@ -168,13 +168,9 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
apiDefinitionExecResultService
.
saveApiResult
(
testResult
);
}
}
else
if
(
StringUtils
.
equals
(
this
.
runMode
,
ApiRunMode
.
SCENARIO
.
name
()))
{
// 调试操作,不需要存储结果
if
(
StringUtils
.
isBlank
(
debugReportId
))
{
apiScenarioReportService
.
addResult
(
testResult
);
}
else
{
apiScenarioReportService
.
addResult
(
testResult
);
//apiScenarioReportService.saveApiResult(testResult);
}
// 执行报告不需要存储,由用户确认后在存储
testResult
.
setTestId
(
debugReportId
);
apiScenarioReportService
.
complete
(
testResult
);
}
else
{
apiTestService
.
changeStatus
(
testId
,
APITestStatus
.
Completed
);
report
=
apiReportService
.
getRunningReport
(
testResult
.
getTestId
());
...
...
backend/src/main/java/io/metersphere/api/service/APIMonitorService.java
0 → 100644
浏览文件 @
1aab595a
package
io.metersphere.api.service
;
import
io.metersphere.api.dto.ApiMonitorSearch
;
import
io.metersphere.api.dto.ApiResponseCodeMonitor
;
import
io.metersphere.api.dto.ApiResponseTimeMonitor
;
import
io.metersphere.base.mapper.ApiDataViewMapper
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
import
javax.annotation.Resource
;
import
java.util.List
;
@Service
@Transactional
(
rollbackFor
=
Exception
.
class
)
public
class
APIMonitorService
{
@Resource
private
ApiDataViewMapper
apiDataViewMapper
;
public
List
<
ApiMonitorSearch
>
list
()
{
return
apiDataViewMapper
.
selectAll
();
}
public
List
<
ApiResponseTimeMonitor
>
getApiResponseTimeData
(
String
apiUrl
,
String
startTime
,
String
endTime
)
{
return
apiDataViewMapper
.
selectResponseTimeByUrl
(
apiUrl
,
startTime
,
endTime
);
}
public
List
<
ApiResponseCodeMonitor
>
getApiResponseCodeData
(
String
apiUrl
,
String
startTime
,
String
endTime
)
{
return
apiDataViewMapper
.
selectResponseCodeByUrl
(
apiUrl
,
startTime
,
endTime
);
}
public
String
getReportId
(
String
apiUrl
,
String
startTime
)
{
return
apiDataViewMapper
.
selectReportIdByUrlAndStartTime
(
apiUrl
,
startTime
);
}
}
backend/src/main/java/io/metersphere/api/service/APIReportService.java
浏览文件 @
1aab595a
package
io.metersphere.api.service
;
import
com.alibaba.fastjson.JSON
;
import
com.alibaba.fastjson.JSONArray
;
import
com.alibaba.fastjson.JSONObject
;
import
io.metersphere.api.dto.APIReportResult
;
import
io.metersphere.api.dto.DeleteAPIReportRequest
;
import
io.metersphere.api.dto.QueryAPIReportRequest
;
import
io.metersphere.api.jmeter.TestResult
;
import
io.metersphere.base.domain.*
;
import
io.metersphere.base.mapper.ApiDataViewMapper
;
import
io.metersphere.base.mapper.ApiTestReportDetailMapper
;
import
io.metersphere.base.mapper.ApiTestReportMapper
;
import
io.metersphere.base.mapper.ext.ExtApiTestReportMapper
;
import
io.metersphere.commons.constants.APITestStatus
;
import
io.metersphere.commons.constants.ReportTriggerMode
;
import
io.metersphere.commons.exception.MSException
;
import
io.metersphere.commons.utils.LogUtil
;
import
io.metersphere.commons.utils.ServiceUtils
;
import
io.metersphere.dto.DashboardTestDTO
;
import
io.metersphere.i18n.Translator
;
...
...
@@ -21,8 +25,11 @@ import org.springframework.transaction.annotation.Transactional;
import
javax.annotation.Resource
;
import
java.nio.charset.StandardCharsets
;
import
java.text.SimpleDateFormat
;
import
java.time.Instant
;
import
java.time.temporal.ChronoUnit
;
import
java.util.ArrayList
;
import
java.util.Date
;
import
java.util.List
;
import
java.util.UUID
;
...
...
@@ -36,6 +43,8 @@ public class APIReportService {
private
ApiTestReportDetailMapper
apiTestReportDetailMapper
;
@Resource
private
ExtApiTestReportMapper
extApiTestReportMapper
;
@Resource
private
ApiDataViewMapper
apiDataViewMapper
;
public
List
<
APIReportResult
>
list
(
QueryAPIReportRequest
request
)
{
request
.
setOrders
(
ServiceUtils
.
getDefaultOrder
(
request
.
getOrders
()));
...
...
@@ -63,6 +72,8 @@ public class APIReportService {
public
void
delete
(
DeleteAPIReportRequest
request
)
{
apiTestReportDetailMapper
.
deleteByPrimaryKey
(
request
.
getId
());
apiTestReportMapper
.
deleteByPrimaryKey
(
request
.
getId
());
apiDataViewMapper
.
deleteByReportId
(
request
.
getId
());
}
public
void
deleteByTestId
(
String
testId
)
{
...
...
@@ -89,6 +100,8 @@ public class APIReportService {
// report
report
.
setUpdateTime
(
System
.
currentTimeMillis
());
if
(!
StringUtils
.
equals
(
report
.
getStatus
(),
APITestStatus
.
Debug
.
name
()))
{
//新增每一条接口记录到api_data_view表中
creatApiDataView
(
new
String
(
detail
.
getContent
(),
StandardCharsets
.
UTF_8
),
report
.
getId
());
if
(
result
.
getError
()
>
0
)
{
report
.
setStatus
(
APITestStatus
.
Error
.
name
());
}
else
{
...
...
@@ -99,6 +112,44 @@ public class APIReportService {
apiTestReportMapper
.
updateByPrimaryKeySelective
(
report
);
}
private
void
creatApiDataView
(
String
jsonString
,
String
reportId
)
{
List
<
ApiDataView
>
listApiDataView
=
new
ArrayList
<>();
JSONObject
jsonObject
=
JSON
.
parseObject
(
jsonString
,
JSONObject
.
class
);
JSONArray
jsonArray
=
jsonObject
.
getJSONArray
(
"scenarios"
);
SimpleDateFormat
sdf
=
new
SimpleDateFormat
(
"yyyy-MM-dd HH:mm:ss"
);
try
{
for
(
int
i
=
0
;
i
<
jsonArray
.
size
();
i
++)
{
JSONObject
jsonInArray
=
jsonArray
.
getJSONObject
(
i
);
JSONArray
jsonRequestResults
=
jsonInArray
.
getJSONArray
(
"requestResults"
);
for
(
int
j
=
0
;
j
<
jsonRequestResults
.
size
();
j
++)
{
JSONObject
jsonInResponseResult
=
jsonRequestResults
.
getJSONObject
(
j
).
getJSONObject
(
"responseResult"
);
String
responseTime
=
jsonInResponseResult
.
getString
(
"responseTime"
);
String
responseCode
=
jsonInResponseResult
.
getString
(
"responseCode"
);
String
startTime
=
jsonRequestResults
.
getJSONObject
(
j
).
getString
(
"startTime"
);
String
name
=
jsonRequestResults
.
getJSONObject
(
j
).
getString
(
"name"
);
String
url
=
jsonRequestResults
.
getJSONObject
(
j
).
getString
(
"url"
);
if
(
StringUtils
.
isBlank
(
url
)){
//如果非http请求不入库
continue
;
}
ApiDataView
apiDataView
=
new
ApiDataView
();
apiDataView
.
setId
(
UUID
.
randomUUID
().
toString
());
apiDataView
.
setReportId
(
reportId
);
apiDataView
.
setApiName
(
name
);
apiDataView
.
setUrl
(
StringUtils
.
substringBefore
(
url
,
"?"
));
apiDataView
.
setResponseTime
(
responseTime
);
apiDataView
.
setStartTime
(
sdf
.
format
(
new
Date
(
Long
.
parseLong
(
startTime
))));
apiDataView
.
setResponseCode
(
responseCode
);
listApiDataView
.
add
(
apiDataView
);
}
}
}
catch
(
Exception
e
)
{
LogUtil
.
error
(
e
);
}
apiDataViewMapper
.
insertListApiData
(
listApiDataView
);
}
public
String
create
(
ApiTest
test
,
String
triggerMode
)
{
ApiTestReport
running
=
getRunningReport
(
test
.
getId
());
if
(
running
!=
null
)
{
...
...
backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java
浏览文件 @
1aab595a
package
io.metersphere.api.service
;
import
com.alibaba.fastjson.JSON
;
import
com.alibaba.fastjson.JSONObject
;
import
com.google.gson.Gson
;
import
io.metersphere.api.dto.APIReportResult
;
import
io.metersphere.api.dto.automation.ApiScenarioDTO
;
import
io.metersphere.api.dto.automation.ApiScenarioRequest
;
import
io.metersphere.api.dto.automation.SaveApiScenarioRequest
;
import
io.metersphere.api.dto.automation.ScenarioStatus
;
import
io.metersphere.api.dto.definition.RunDefinitionRequest
;
import
io.metersphere.api.dto.scenario.environment.EnvironmentConfig
;
import
io.metersphere.api.jmeter.JMeterService
;
import
io.metersphere.base.domain.ApiScenario
;
import
io.metersphere.base.domain.ApiScenarioExample
;
import
io.metersphere.base.domain.ApiTag
;
import
io.metersphere.base.domain.ApiTagExample
;
import
io.metersphere.base.domain.*
;
import
io.metersphere.base.mapper.ApiScenarioMapper
;
import
io.metersphere.base.mapper.ApiTagMapper
;
import
io.metersphere.base.mapper.ext.ExtApiScenarioMapper
;
import
io.metersphere.commons.constants.APITestStatus
;
import
io.metersphere.commons.constants.ApiRunMode
;
import
io.metersphere.commons.exception.MSException
;
import
io.metersphere.commons.utils.LogUtil
;
import
io.metersphere.commons.utils.SessionUtils
;
import
io.metersphere.i18n.Translator
;
import
org.apache.commons.collections.CollectionUtils
;
import
org.apache.commons.lang3.StringUtils
;
import
org.apache.jorphan.collections.HashTree
;
import
org.aspectj.util.FileUtil
;
import
org.springframework.stereotype.Service
;
...
...
@@ -32,6 +34,7 @@ import java.io.*;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.UUID
;
import
java.util.stream.Collectors
;
@Service
...
...
@@ -45,6 +48,10 @@ public class ApiAutomationService {
private
ApiTagMapper
apiTagMapper
;
@Resource
private
JMeterService
jMeterService
;
@Resource
private
ApiTestEnvironmentService
environmentService
;
@Resource
private
ApiScenarioReportService
apiReportService
;
private
static
final
String
BODY_FILE_DIR
=
"/opt/metersphere/data/body"
;
...
...
@@ -92,7 +99,11 @@ public class ApiAutomationService {
scenario
.
setScenarioDefinition
(
request
.
getScenarioDefinition
());
scenario
.
setCreateTime
(
System
.
currentTimeMillis
());
scenario
.
setUpdateTime
(
System
.
currentTimeMillis
());
scenario
.
setStatus
(
ScenarioStatus
.
Saved
.
name
());
if
(
StringUtils
.
isNotEmpty
(
request
.
getStatus
()))
{
scenario
.
setStatus
(
request
.
getStatus
());
}
else
{
scenario
.
setStatus
(
ScenarioStatus
.
Underway
.
name
());
}
if
(
request
.
getUserId
()
==
null
)
{
scenario
.
setUserId
(
SessionUtils
.
getUserId
());
}
else
{
...
...
@@ -117,7 +128,11 @@ public class ApiAutomationService {
scenario
.
setStepTotal
(
request
.
getStepTotal
());
scenario
.
setScenarioDefinition
(
request
.
getScenarioDefinition
());
scenario
.
setUpdateTime
(
System
.
currentTimeMillis
());
scenario
.
setStatus
(
ScenarioStatus
.
Saved
.
name
());
if
(
StringUtils
.
isNotEmpty
(
request
.
getStatus
()))
{
scenario
.
setStatus
(
request
.
getStatus
());
}
else
{
scenario
.
setStatus
(
ScenarioStatus
.
Underway
.
name
());
}
scenario
.
setUserId
(
request
.
getUserId
());
scenario
.
setDescription
(
request
.
getDescription
());
apiScenarioMapper
.
updateByPrimaryKeySelective
(
scenario
);
...
...
@@ -143,8 +158,7 @@ public class ApiAutomationService {
private
void
checkNameExist
(
SaveApiScenarioRequest
request
)
{
ApiScenarioExample
example
=
new
ApiScenarioExample
();
example
.
createCriteria
().
andNameEqualTo
(
request
.
getName
()).
andProjectIdEqualTo
(
request
.
getProjectId
())
.
andApiScenarioModuleIdEqualTo
(
request
.
getApiScenarioModuleId
()).
andIdNotEqualTo
(
request
.
getId
());
example
.
createCriteria
().
andNameEqualTo
(
request
.
getName
()).
andProjectIdEqualTo
(
request
.
getProjectId
()).
andIdNotEqualTo
(
request
.
getId
());
if
(
apiScenarioMapper
.
countByExample
(
example
)
>
0
)
{
MSException
.
throwException
(
Translator
.
get
(
"automation_name_already_exists"
));
}
...
...
@@ -204,10 +218,26 @@ public class ApiAutomationService {
public
String
run
(
RunDefinitionRequest
request
,
List
<
MultipartFile
>
bodyFiles
)
{
List
<
String
>
bodyUploadIds
=
new
ArrayList
<>(
request
.
getBodyUploadIds
());
createBodyFiles
(
bodyUploadIds
,
bodyFiles
);
HashTree
hashTree
=
request
.
getTestElement
().
generateHashTree
();
EnvironmentConfig
config
=
null
;
if
(
request
.
getEnvironmentId
()
!=
null
)
{
ApiTestEnvironmentWithBLOBs
environment
=
environmentService
.
get
(
request
.
getEnvironmentId
());
config
=
JSONObject
.
parseObject
(
environment
.
getConfig
(),
EnvironmentConfig
.
class
);
}
HashTree
hashTree
=
request
.
getTestElement
().
generateHashTree
(
config
);
request
.
getTestElement
().
getJmx
(
hashTree
);
// 调用执行方法
jMeterService
.
runDefinition
(
request
.
getId
(),
hashTree
,
request
.
getReportId
(),
ApiRunMode
.
SCENARIO
.
name
());
APIReportResult
report
=
new
APIReportResult
();
report
.
setId
(
UUID
.
randomUUID
().
toString
());
report
.
setTestId
(
request
.
getReportId
());
report
.
setName
(
"RUN"
);
report
.
setTriggerMode
(
null
);
report
.
setCreateTime
(
System
.
currentTimeMillis
());
report
.
setUpdateTime
(
System
.
currentTimeMillis
());
report
.
setStatus
(
APITestStatus
.
Running
.
name
());
report
.
setUserId
(
SessionUtils
.
getUserId
());
apiReportService
.
addResult
(
report
);
return
request
.
getId
();
}
...
...
backend/src/main/java/io/metersphere/api/service/ApiScenarioReportService.java
浏览文件 @
1aab595a
package
io.metersphere.api.service
;
import
com.alibaba.fastjson.JSONObject
;
import
io.metersphere.api.dto.APIReportResult
;
import
io.metersphere.api.jmeter.TestResult
;
import
io.metersphere.base.domain.ApiTestReportDetail
;
import
io.metersphere.commons.constants.APITestStatus
;
import
io.metersphere.commons.exception.MSException
;
import
io.metersphere.i18n.Translator
;
import
org.apache.commons.lang3.StringUtils
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
import
sun.security.util.Cache
;
import
java.nio.charset.StandardCharsets
;
@Service
@Transactional
(
rollbackFor
=
Exception
.
class
)
public
class
ApiScenarioReportService
{
private
static
Cache
cache
=
Cache
.
newHardMemoryCache
(
0
,
3600
*
24
);
public
void
addResult
(
TestResult
res
)
{
if
(!
res
.
getScenarios
().
isEmpty
())
{
cache
.
put
(
res
.
getTestId
(),
res
);
}
else
{
MSException
.
throwException
(
Translator
.
get
(
"test_not_found"
));
public
void
complete
(
TestResult
result
)
{
Object
obj
=
cache
.
get
(
result
.
getTestId
());
if
(
obj
==
null
)
{
MSException
.
throwException
(
Translator
.
get
(
"api_report_is_null"
));
}
APIReportResult
report
=
(
APIReportResult
)
obj
;
// report detail
ApiTestReportDetail
detail
=
new
ApiTestReportDetail
();
detail
.
setReportId
(
result
.
getTestId
());
detail
.
setTestId
(
report
.
getTestId
());
detail
.
setContent
(
JSONObject
.
toJSONString
(
result
).
getBytes
(
StandardCharsets
.
UTF_8
));
// report
report
.
setUpdateTime
(
System
.
currentTimeMillis
());
if
(!
StringUtils
.
equals
(
report
.
getStatus
(),
APITestStatus
.
Debug
.
name
()))
{
if
(
result
.
getError
()
>
0
)
{
report
.
setStatus
(
APITestStatus
.
Error
.
name
());
}
else
{
report
.
setStatus
(
APITestStatus
.
Success
.
name
());
}
}
report
.
setContent
(
new
String
(
detail
.
getContent
(),
StandardCharsets
.
UTF_8
));
cache
.
put
(
report
.
getTestId
(),
report
);
}
public
void
addResult
(
APIReportResult
res
)
{
cache
.
put
(
res
.
getTestId
(),
res
);
}
/**
* 获取零时执行报告
*
* @param testId
*/
public
APIReportResult
getCacheResult
(
String
testId
)
{
Object
res
=
cache
.
get
(
testId
);
if
(
res
!=
null
)
{
APIReportResult
reportResult
=
(
APIReportResult
)
res
;
if
(!
reportResult
.
getStatus
().
equals
(
APITestStatus
.
Running
.
name
()))
{
cache
.
remove
(
testId
);
}
return
reportResult
;
}
return
null
;
}
}
backend/src/main/java/io/metersphere/base/domain/ApiDataView.java
0 → 100644
浏览文件 @
1aab595a
package
io.metersphere.base.domain
;
import
lombok.Data
;
import
java.io.Serializable
;
@Data
public
class
ApiDataView
implements
Serializable
{
private
String
id
;
private
String
reportId
;
private
String
url
;
private
String
apiName
;
private
String
startTime
;
private
String
responseCode
;
private
String
responseTime
;
private
static
final
long
serialVersionUID
=
1L
;
}
backend/src/main/java/io/metersphere/base/mapper/ApiDataViewMapper.java
0 → 100644
浏览文件 @
1aab595a
package
io.metersphere.base.mapper
;
import
io.metersphere.api.dto.ApiMonitorSearch
;
import
io.metersphere.api.dto.ApiResponseCodeMonitor
;
import
io.metersphere.api.dto.ApiResponseTimeMonitor
;
import
io.metersphere.base.domain.ApiDataView
;
import
java.util.List
;
public
interface
ApiDataViewMapper
{
List
<
ApiMonitorSearch
>
selectAll
();
List
<
ApiResponseTimeMonitor
>
selectResponseTimeByUrl
(
String
url
,
String
startTime
,
String
endTime
);
List
<
ApiResponseCodeMonitor
>
selectResponseCodeByUrl
(
String
url
,
String
startTime
,
String
endTime
);
Integer
insertListApiData
(
List
<
ApiDataView
>
list
);
Integer
deleteByReportId
(
String
reportId
);
String
selectReportIdByUrlAndStartTime
(
String
apiUrl
,
String
startTime
);
}
\ No newline at end of file
backend/src/main/java/io/metersphere/base/mapper/ApiDataViewMapper.xml
0 → 100644
浏览文件 @
1aab595a
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper
namespace=
"io.metersphere.base.mapper.ApiDataViewMapper"
>
<resultMap
id=
"apiDataView"
type=
"io.metersphere.base.domain.ApiDataView"
>
<id
column=
"id"
property=
"id"
/>
<result
column=
"report_id"
property=
"reportId"
/>
<result
column=
"api_name"
property=
"apiName"
/>
<result
column=
"url"
property=
"url"
/>
<result
column=
"response_code"
property=
"startTime"
/>
<result
column=
"start_time"
property=
"responseCode"
/>
<result
column=
"response_time"
property=
"responseTime"
/>
</resultMap>
<delete
id=
"deleteByReportId"
parameterType=
"java.lang.String"
>
delete from api_data_view where report_id = #{reportId,jdbcType=VARCHAR}
</delete>
<select
id=
"selectAll"
resultType=
"io.metersphere.api.dto.ApiMonitorSearch"
>
select distinct url from api_data_view;
</select>
<select
id=
"selectResponseTimeByUrl"
parameterType=
"java.lang.String"
resultType=
"io.metersphere.api.dto.ApiResponseTimeMonitor"
>
select id,report_id,api_name,start_time,response_time,url
from api_data_view
<where>
<if
test=
"url != null and url != ''"
>
url = #{url}
</if>
<if
test=
"startTime != null and startTime != '' and endTime != null and endTime != ''"
>
AND date_format(start_time,'%Y-%m-%d %H:%i:%s')
between #{startTime} and #{endTime}
</if>
<if
test=
"startTime == null or startTime == '' or endTime == null or endTime == ''"
>
AND TO_DAYS(start_time) =TO_DAYS(NOW())
</if>
order by start_time;
</where>
</select>
<select
id=
"selectResponseCodeByUrl"
resultType=
"io.metersphere.api.dto.ApiResponseCodeMonitor"
>
select id,report_id,api_name,start_time,response_code,url
from api_data_view
<where>
<if
test=
"url != null and url != ''"
>
url = #{url}
</if>
<if
test=
"startTime != null and startTime != '' and endTime != null and endTime != ''"
>
AND date_format(start_time,'%Y-%m-%d %H:%i:%s')
between #{startTime} and #{endTime} and length(response_code)=3
</if>
<if
test=
"startTime == null or startTime == '' or endTime == null or endTime == ''"
>
AND TO_DAYS(start_time) =TO_DAYS(NOW()) and length(response_code)=3
</if>
order by start_time;
</where>
</select>
<select
id=
"selectReportIdByUrlAndStartTime"
resultType=
"java.lang.String"
>
select report_id from api_data_view where response_code != 200 and url=#{apiUrl} and start_time=#{startTime};
</select>
<insert
id=
"insertListApiData"
parameterType=
"java.util.List"
>
insert into api_data_view(id, report_id, api_name,url, response_code, start_time,response_time)
values
<foreach
collection=
"list"
item=
"item"
index=
"index"
separator=
","
>
(
#{item.id,jdbcType=VARCHAR},
#{item.reportId,jdbcType=VARCHAR},
#{item.apiName,jdbcType=VARCHAR},
#{item.url,jdbcType=VARCHAR},
#{item.responseCode,jdbcType=VARCHAR},
#{item.startTime,jdbcType=VARCHAR},
#{item.responseTime,jdbcType=VARCHAR}
)
</foreach>
</insert>
</mapper>
\ No newline at end of file
backend/src/main/resources/db/migration/V47__api_data_view.sql
0 → 100644
浏览文件 @
1aab595a
CREATE
TABLE
IF
NOT
EXISTS
`api_data_view`
(
id
varchar
(
50
)
NOT
NULL
primary
key
,
report_id
varchar
(
255
)
NOT
NULL
,
api_name
varchar
(
200
)
NULL
,
url
varchar
(
255
)
NULL
,
response_code
varchar
(
100
)
NULL
,
start_time
varchar
(
20
)
NULL
,
response_time
varchar
(
20
)
default
'0'
NULL
,
create_time
timestamp
default
CURRENT_TIMESTAMP
NOT
NULL
,
update_time
timestamp
default
CURRENT_TIMESTAMP
NOT
NULL
)
ENGINE
=
InnoDB
DEFAULT
CHARSET
=
utf8mb4
;
\ No newline at end of file
frontend/src/business/components/api/automation/ApiAutomation.vue
浏览文件 @
1aab595a
...
...
@@ -21,7 +21,7 @@
:name=
"item.name"
closable
>
<div
class=
"ms-api-scenario-div"
>
<ms-edit-api-scenario
:current-project=
"currentProject"
:currentScenario=
"currentScenario"
:moduleOptions=
"moduleOptions"
/>
<ms-edit-api-scenario
:current-project=
"currentProject"
:currentScenario=
"
item.
currentScenario"
:moduleOptions=
"moduleOptions"
/>
</div>
</el-tab-pane>
...
...
@@ -37,77 +37,82 @@
<
script
>
import
MsContainer
from
"
@/business/components/common/components/MsContainer
"
;
import
MsAsideContainer
from
"
@/business/components/common/components/MsAsideContainer
"
;
import
MsMainContainer
from
"
@/business/components/common/components/MsMainContainer
"
;
import
MsApiScenarioList
from
"
@/business/components/api/automation/scenario/ApiScenarioList
"
;
import
{
getUUID
}
from
"
@/common/js/utils
"
;
import
MsApiScenarioModule
from
"
@/business/components/api/automation/scenario/ApiScenarioModule
"
;
import
MsEditApiScenario
from
"
./scenario/EditApiScenario
"
;
import
MsContainer
from
"
@/business/components/common/components/MsContainer
"
;
import
MsAsideContainer
from
"
@/business/components/common/components/MsAsideContainer
"
;
import
MsMainContainer
from
"
@/business/components/common/components/MsMainContainer
"
;
import
MsApiScenarioList
from
"
@/business/components/api/automation/scenario/ApiScenarioList
"
;
import
{
getUUID
}
from
"
@/common/js/utils
"
;
import
MsApiScenarioModule
from
"
@/business/components/api/automation/scenario/ApiScenarioModule
"
;
import
MsEditApiScenario
from
"
./scenario/EditApiScenario
"
;
export
default
{
name
:
"
ApiAutomation
"
,
components
:
{
MsApiScenarioModule
,
MsApiScenarioList
,
MsMainContainer
,
MsAsideContainer
,
MsContainer
,
MsEditApiScenario
},
comments
:
{},
data
()
{
return
{
isHide
:
true
,
activeName
:
'
default
'
,
currentProject
:
null
,
currentModule
:
null
,
currentScenario
:
{},
moduleOptions
:
{},
tabs
:
[],
}
},
watch
:
{},
methods
:
{
addTab
(
tab
)
{
if
(
tab
.
name
===
'
add
'
)
{
let
label
=
this
.
$t
(
'
api_test.automation.add_scenario
'
);
let
name
=
getUUID
().
substring
(
0
,
8
);
this
.
tabs
.
push
({
label
:
label
,
name
:
name
});
this
.
activeName
=
name
;
}
},
removeTab
(
targetName
)
{
this
.
tabs
=
this
.
tabs
.
filter
(
tab
=>
tab
.
name
!==
targetName
);
if
(
this
.
tabs
.
length
>
0
)
{
this
.
activeName
=
this
.
tabs
[
this
.
tabs
.
length
-
1
].
name
;
}
else
{
this
.
activeName
=
"
default
"
export
default
{
name
:
"
ApiAutomation
"
,
components
:
{
MsApiScenarioModule
,
MsApiScenarioList
,
MsMainContainer
,
MsAsideContainer
,
MsContainer
,
MsEditApiScenario
},
comments
:
{},
data
()
{
return
{
isHide
:
true
,
activeName
:
'
default
'
,
currentProject
:
null
,
currentModule
:
null
,
moduleOptions
:
{},
tabs
:
[],
}
},
setTabLabel
(
data
)
{
for
(
const
tab
of
this
.
tabs
)
{
if
(
tab
.
name
===
this
.
activeName
)
{
tab
.
label
=
data
.
name
;
break
;
watch
:
{},
methods
:
{
addTab
(
tab
)
{
if
(
tab
.
name
===
'
add
'
)
{
let
label
=
this
.
$t
(
'
api_test.automation.add_scenario
'
);
let
name
=
getUUID
().
substring
(
0
,
8
);
this
.
activeName
=
name
;
this
.
tabs
.
push
({
label
:
label
,
name
:
name
,
currentScenario
:
{}});
}
}
},
selectModule
(
data
)
{
this
.
currentModule
=
data
;
},
saveScenario
(
data
)
{
this
.
setTabLabel
(
data
);
this
.
$refs
.
apiScenarioList
.
search
(
data
);
},
initTree
(
data
)
{
this
.
moduleOptions
=
data
;
},
changeProject
(
data
)
{
this
.
currentProject
=
data
;
},
refresh
(
data
)
{
this
.
$refs
.
apiScenarioList
.
search
(
data
);
},
editScenario
(
row
)
{
this
.
currentScenario
=
row
;
this
.
addTab
({
name
:
'
add
'
});
},
if
(
tab
.
name
===
'
edit
'
)
{
let
label
=
this
.
$t
(
'
api_test.automation.add_scenario
'
);
let
name
=
getUUID
().
substring
(
0
,
8
);
this
.
activeName
=
name
;
label
=
tab
.
currentScenario
.
name
;
this
.
tabs
.
push
({
label
:
label
,
name
:
name
,
currentScenario
:
tab
.
currentScenario
});
}
},
removeTab
(
targetName
)
{
this
.
tabs
=
this
.
tabs
.
filter
(
tab
=>
tab
.
name
!==
targetName
);
if
(
this
.
tabs
.
length
>
0
)
{
this
.
activeName
=
this
.
tabs
[
this
.
tabs
.
length
-
1
].
name
;
}
else
{
this
.
activeName
=
"
default
"
}
},
setTabLabel
(
data
)
{
for
(
const
tab
of
this
.
tabs
)
{
if
(
tab
.
name
===
this
.
activeName
)
{
tab
.
label
=
data
.
name
;
break
;
}
}
},
selectModule
(
data
)
{
this
.
currentModule
=
data
;
},
saveScenario
(
data
)
{
this
.
setTabLabel
(
data
);
this
.
$refs
.
apiScenarioList
.
search
(
data
);
},
initTree
(
data
)
{
this
.
moduleOptions
=
data
;
},
changeProject
(
data
)
{
this
.
currentProject
=
data
;
},
refresh
(
data
)
{
this
.
$refs
.
apiScenarioList
.
search
(
data
);
},
editScenario
(
row
)
{
this
.
addTab
({
name
:
'
edit
'
,
currentScenario
:
row
});
},
}
}
}
</
script
>
<
style
scoped
>
...
...
frontend/src/business/components/api/automation/report/ApiReport.vue
已删除
100644 → 0
浏览文件 @
7ba3c09a
<
template
>
<div>
ApiReport
</div>
</
template
>
<
script
>
export
default
{
name
:
"
ApiReport
"
,
components
:
{},
comments
:
{},
data
()
{
return
{}
},
watch
:
{},
methods
:
{}
}
</
script
>
<
style
scoped
>
</
style
>
frontend/src/business/components/api/automation/report/ApiReportDetail.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<ms-container
v-loading=
"loading"
>
<ms-main-container>
<el-card>
<section
class=
"report-container"
v-if=
"this.report.testId"
>
<ms-api-report-view-header
:report=
"report"
@
reportExport=
"handleExport"
/>
<main
v-if=
"this.isNotRunning"
>
<ms-metric-chart
:content=
"content"
:totalTime=
"totalTime"
/>
<div
@
click=
"active"
>
<ms-scenario-results
:scenarios=
"content.scenarios"
v-on:requestResult=
"requestResult"
/>
</div>
<el-collapse-transition>
<div
v-show=
"isActive"
style=
"width: 99%"
>
<ms-request-result-tail
v-if=
"isRequestResult"
:request-type=
"requestType"
:request=
"request"
:scenario-name=
"scenarioName"
/>
</div>
</el-collapse-transition>
<ms-api-report-export
v-if=
"reportExportVisible"
id=
"apiTestReport"
:title=
"report.testName"
:content=
"content"
:total-time=
"totalTime"
/>
</main>
</section>
</el-card>
</ms-main-container>
</ms-container>
</
template
>
<
script
>
import
MsRequestResult
from
"
./components/RequestResult
"
;
import
MsRequestResultTail
from
"
./components/RequestResultTail
"
;
import
MsScenarioResult
from
"
./components/ScenarioResult
"
;
import
MsMetricChart
from
"
./components/MetricChart
"
;
import
MsScenarioResults
from
"
./components/ScenarioResults
"
;
import
MsContainer
from
"
@/business/components/common/components/MsContainer
"
;
import
MsMainContainer
from
"
@/business/components/common/components/MsMainContainer
"
;
import
MsApiReportExport
from
"
./ApiReportExport
"
;
import
MsApiReportViewHeader
from
"
./ApiReportViewHeader
"
;
import
{
RequestFactory
}
from
"
../../definition/model/ApiTestModel
"
;
import
{
windowPrint
}
from
"
@/common/js/utils
"
;
export
default
{
name
:
"
MsApiReport
"
,
components
:
{
MsApiReportViewHeader
,
MsApiReportExport
,
MsMainContainer
,
MsContainer
,
MsScenarioResults
,
MsRequestResultTail
,
MsMetricChart
,
MsScenarioResult
,
MsRequestResult
},
data
()
{
return
{
activeName
:
"
total
"
,
content
:
{},
report
:
{},
loading
:
true
,
fails
:
[],
totalTime
:
0
,
isRequestResult
:
false
,
request
:
{},
isActive
:
false
,
scenarioName
:
null
,
reportExportVisible
:
false
,
requestType
:
undefined
,
}
},
props
:
[
'
reportId
'
],
activated
()
{
this
.
isRequestResult
=
false
;
},
watch
:
{
reportId
()
{
this
.
getReport
();
}
},
methods
:
{
init
()
{
this
.
loading
=
true
;
this
.
report
=
{};
this
.
content
=
{};
this
.
fails
=
[];
this
.
report
=
{};
this
.
isRequestResult
=
false
;
},
handleClick
(
tab
,
event
)
{
this
.
isRequestResult
=
false
},
active
()
{
this
.
isActive
=
!
this
.
isActive
;
},
getReport
()
{
this
.
init
();
if
(
this
.
reportId
)
{
let
url
=
"
/api/scenario/report/get/
"
+
this
.
reportId
;
this
.
$get
(
url
,
response
=>
{
this
.
report
=
response
.
data
||
{};
if
(
response
.
data
)
{
if
(
this
.
isNotRunning
)
{
try
{
this
.
content
=
JSON
.
parse
(
this
.
report
.
content
);
}
catch
(
e
)
{
// console.log(this.report.content)
throw
e
;
}
this
.
getFails
();
this
.
loading
=
false
;
}
else
{
setTimeout
(
this
.
getReport
,
2000
)
}
}
else
{
this
.
loading
=
false
;
this
.
$error
(
this
.
$t
(
'
api_report.not_exist
'
));
}
});
}
},
getFails
()
{
if
(
this
.
isNotRunning
)
{
this
.
fails
=
[];
this
.
totalTime
=
0
this
.
content
.
scenarios
.
forEach
((
scenario
)
=>
{
this
.
totalTime
=
this
.
totalTime
+
Number
(
scenario
.
responseTime
)
let
failScenario
=
Object
.
assign
({},
scenario
);
if
(
scenario
.
error
>
0
)
{
this
.
fails
.
push
(
failScenario
);
failScenario
.
requestResults
=
[];
scenario
.
requestResults
.
forEach
((
request
)
=>
{
if
(
!
request
.
success
)
{
let
failRequest
=
Object
.
assign
({},
request
);
failScenario
.
requestResults
.
push
(
failRequest
);
}
})
}
})
}
},
requestResult
(
requestResult
)
{
this
.
isRequestResult
=
false
;
this
.
requestType
=
undefined
;
if
(
requestResult
.
request
.
body
.
indexOf
(
'
[Callable Statement]
'
)
>
-
1
)
{
this
.
requestType
=
RequestFactory
.
TYPES
.
SQL
;
}
this
.
$nextTick
(
function
()
{
this
.
isRequestResult
=
true
;
this
.
request
=
requestResult
.
request
;
this
.
scenarioName
=
requestResult
.
scenarioName
;
});
},
handleExport
()
{
this
.
reportExportVisible
=
true
;
let
reset
=
this
.
exportReportReset
;
this
.
$nextTick
(()
=>
{
windowPrint
(
'
apiTestReport
'
,
0.57
);
reset
();
});
},
exportReportReset
()
{
this
.
$router
.
go
(
0
);
}
},
created
()
{
this
.
getReport
();
},
computed
:
{
path
()
{
return
"
/api/test/edit?id=
"
+
this
.
report
.
testId
;
},
isNotRunning
()
{
return
"
Running
"
!==
this
.
report
.
status
;
}
}
}
</
script
>
<
style
>
.report-container
.el-tabs__header
{
margin-bottom
:
1px
;
}
</
style
>
<
style
scoped
>
.report-container
{
height
:
calc
(
100vh
-
155px
);
min-height
:
600px
;
overflow-y
:
auto
;
}
.report-header
{
font-size
:
15px
;
}
.report-header
a
{
text-decoration
:
none
;
}
.report-header
.time
{
color
:
#909399
;
margin-left
:
10px
;
}
.report-container
.fail
{
color
:
#F56C6C
;
}
.report-container
.is-active
.fail
{
color
:
inherit
;
}
.export-button
{
float
:
right
;
}
.scenario-result
.icon.is-active
{
transform
:
rotate
(
90deg
);
}
</
style
>
frontend/src/business/components/api/automation/report/ApiReportExport.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<ms-report-export-template
:title=
"title"
:type=
"$t('report.api_test_report')"
>
<ms-metric-chart
:content=
"content"
:totalTime=
"totalTime"
/>
<div
class=
"scenario-result"
v-for=
"(scenario, index) in content.scenarios"
:key=
"index"
:scenario=
"scenario"
>
<div>
<el-card>
<template
v-slot:header
>
{{
$t
(
'
api_report.scenario_name
'
)
}}
:
{{
scenario
.
name
}}
</
template
>
<div
class=
"ms-border clearfix"
v-for=
"(request, index) in scenario.requestResults"
:key=
"index"
:request=
"request"
>
<div
class=
"request-top"
>
<div>
{{request.name}}
</div>
<div
class=
"url"
>
{{request.url}}
</div>
</div>
<el-divider/>
<div
class=
"request-bottom"
>
<api-report-reqest-header-item
:title=
"$t('api_test.request.method')"
>
<span
class=
"method"
>
{{request.method}}
</span>
</api-report-reqest-header-item>
<api-report-reqest-header-item
:title=
"$t('api_report.response_time')"
>
{{request.responseResult.responseTime}} ms
</api-report-reqest-header-item>
<api-report-reqest-header-item
:title=
"$t('api_report.latency')"
>
{{request.responseResult.latency}} ms
</api-report-reqest-header-item>
<api-report-reqest-header-item
:title=
"$t('api_report.request_size')"
>
{{request.requestSize}} bytes
</api-report-reqest-header-item>
<api-report-reqest-header-item
:title=
"$t('api_report.response_size')"
>
{{request.responseResult.responseSize}} bytes
</api-report-reqest-header-item>
<api-report-reqest-header-item
:title=
"$t('api_report.error')"
>
{{request.error}}
</api-report-reqest-header-item>
<api-report-reqest-header-item
:title=
"$t('api_report.assertions')"
>
{{request.passAssertions + " / " + request.totalAssertions}}
</api-report-reqest-header-item>
<api-report-reqest-header-item
:title=
"$t('api_report.response_code')"
>
{{request.responseResult.responseCode}}
</api-report-reqest-header-item>
<api-report-reqest-header-item
:title=
"$t('api_report.result')"
>
<el-tag
size=
"mini"
type=
"success"
v-if=
"request.success"
>
{{$t('api_report.success')}}
</el-tag>
<el-tag
size=
"mini"
type=
"danger"
v-else
>
{{$t('api_report.fail')}}
</el-tag>
</api-report-reqest-header-item>
</div>
</div>
</el-card>
</div>
</div>
</ms-report-export-template>
</template>
<
script
>
import
MsScenarioResult
from
"
./components/ScenarioResult
"
;
import
MsRequestResultTail
from
"
./components/RequestResultTail
"
;
import
ApiReportReqestHeaderItem
from
"
./ApiReportReqestHeaderItem
"
;
import
MsMetricChart
from
"
./components/MetricChart
"
;
import
MsReportTitle
from
"
../../../common/components/report/MsReportTitle
"
;
import
MsReportExportTemplate
from
"
../../../common/components/report/MsReportExportTemplate
"
;
export
default
{
name
:
"
MsApiReportExport
"
,
components
:
{
MsReportExportTemplate
,
MsReportTitle
,
MsMetricChart
,
ApiReportReqestHeaderItem
,
MsRequestResultTail
,
MsScenarioResult
},
props
:
{
content
:
Object
,
totalTime
:
Number
,
title
:
String
},
data
()
{
return
{}
},
}
</
script
>
<
style
scoped
>
.scenario-result
{
margin-top
:
20px
;
margin-bottom
:
20px
;
}
.method
{
color
:
#1E90FF
;
font-size
:
14px
;
font-weight
:
500
;
}
.request-top
,
.request-bottom
{
margin-left
:
20px
;
}
.url
{
color
:
#409EFF
;
font-size
:
14px
;
font-weight
:
bold
;
font-style
:
italic
;
}
.el-card
{
border-style
:
none
;
padding
:
10px
30px
;
}
.request-top
div
{
margin-top
:
10px
;
}
</
style
>
frontend/src/business/components/api/automation/report/ApiReportReqestHeaderItem.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<div
class=
"item"
>
<div
class=
"item-title"
>
{{
title
}}
</div>
<div>
<slot></slot>
</div>
</div>
</
template
>
<
script
>
export
default
{
name
:
"
ApiReportReqestHeaderItem
"
,
props
:
{
title
:
String
}
}
</
script
>
<
style
scoped
>
.item
{
width
:
120px
;
height
:
50px
;
display
:
inline-block
;
}
.item-title
{
margin-bottom
:
20px
;
}
</
style
>
frontend/src/business/components/api/automation/report/ApiReportStatus.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<div>
<el-tag
size=
"mini"
type=
"info"
v-if=
"row.status === 'Starting'"
>
{{
row
.
status
}}
</el-tag>
<el-tag
size=
"mini"
type=
"primary"
effect=
"plain"
v-else-if=
"row.status === 'Running'"
>
{{
row
.
status
}}
</el-tag>
<el-tag
size=
"mini"
type=
"success"
v-else-if=
"row.status === 'Success'"
>
{{
row
.
status
}}
</el-tag>
<el-tag
size=
"mini"
type=
"warning"
v-else-if=
"row.status === 'Reporting'"
>
{{
row
.
status
}}
</el-tag>
<el-tooltip
placement=
"top"
v-else-if=
"row.status === 'Error'"
effect=
"light"
>
<template
v-slot:content
>
<div>
{{
row
.
description
}}
</div>
</
template
>
<el-tag
size=
"mini"
type=
"danger"
>
{{ row.status }}
</el-tag>
</el-tooltip>
<el-tag
v-else
size=
"mini"
type=
"info"
>
{{ row.status }}
</el-tag>
</div>
</template>
<
script
>
export
default
{
name
:
"
MsApiReportStatus
"
,
props
:
{
row
:
Object
}
}
</
script
>
<
style
scoped
>
</
style
>
frontend/src/business/components/api/automation/report/ApiReportViewHeader.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<header
class=
"report-header"
>
<el-row>
<el-col>
<span>
{{
report
.
projectName
===
null
||
report
.
projectName
===
''
?
"
场景执行报告
"
:
report
.
projectName
}}
/
</span>
<router-link
:to=
"path"
>
{{
report
.
testName
}}
</router-link>
<span
class=
"time"
>
{{
report
.
createTime
|
timestampFormatDate
}}
</span>
<el-button
:disabled=
"isReadOnly"
class=
"export-button"
plain
type=
"primary"
size=
"mini"
@
click=
"handleExport(report.name)"
style=
"margin-left: 1200px"
>
{{
$t
(
'
test_track.plan_view.export_report
'
)
}}
</el-button>
</el-col>
</el-row>
</header>
</
template
>
<
script
>
import
{
checkoutTestManagerOrTestUser
}
from
"
@/common/js/utils
"
;
export
default
{
name
:
"
MsApiReportViewHeader
"
,
props
:
[
'
report
'
],
computed
:
{
path
()
{
return
"
/api/test/edit?id=
"
+
this
.
report
.
testId
;
},
},
data
()
{
return
{
isReadOnly
:
false
,
}
},
created
()
{
if
(
!
checkoutTestManagerOrTestUser
())
{
this
.
isReadOnly
=
true
;
}
},
methods
:
{
handleExport
(
name
)
{
this
.
$emit
(
'
reportExport
'
,
name
);
}
}
}
</
script
>
<
style
scoped
>
.export-button
{
float
:
right
;
}
</
style
>
frontend/src/business/components/api/automation/report/components/AssertionResults.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<el-table
:data=
"assertions"
:row-style=
"getRowStyle"
:header-cell-style=
"getRowStyle"
>
<el-table-column
prop=
"name"
:label=
"$t('api_report.assertions_name')"
width=
"300"
/>
<el-table-column
prop=
"message"
:label=
"$t('api_report.assertions_error_message')"
/>
<el-table-column
prop=
"pass"
:label=
"$t('api_report.assertions_is_success')"
width=
"180"
>
<template
v-slot:default=
"
{row}">
<el-tag
size=
"mini"
type=
"success"
v-if=
"row.pass"
>
{{
$t
(
'
api_report.success
'
)
}}
</el-tag>
<el-tag
size=
"mini"
type=
"danger"
v-else
>
{{
$t
(
'
api_report.fail
'
)
}}
</el-tag>
</
template
>
</el-table-column>
</el-table>
</template>
<
script
>
export
default
{
name
:
"
MsAssertionResults
"
,
props
:
{
assertions
:
Array
},
methods
:
{
getRowStyle
()
{
return
{
backgroundColor
:
"
#F5F5F5
"
};
}
}
}
</
script
>
<
style
scoped
>
</
style
>
frontend/src/business/components/api/automation/report/components/MetricChart.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<div
class=
"metric-container"
>
<el-row
type=
"flex"
align=
"middle"
>
<div
style=
"width: 50%"
>
<el-row
type=
"flex"
justify=
"center"
align=
"middle"
>
<el-row>
<div
class=
"metric-time"
>
<div
class=
"value"
style=
"margin-right: 50px"
>
{{
time
}}
</div>
</div>
</el-row>
<ms-chart
id=
"chart"
ref=
"chart"
:options=
"options"
:autoresize=
"true"
></ms-chart>
<el-row
type=
"flex"
justify=
"center"
align=
"middle"
>
<i
class=
"circle success"
/>
<div
class=
"metric-box"
>
<div
class=
"value"
>
{{
content
.
success
}}
</div>
<div
class=
"name"
>
{{
$t
(
'
api_report.success
'
)
}}
</div>
</div>
<div
style=
"width: 40px"
></div>
<i
class=
"circle fail"
/>
<div
class=
"metric-box"
>
<div
class=
"value"
>
{{
content
.
error
}}
</div>
<div
class=
"name"
>
{{
$t
(
'
api_report.fail
'
)
}}
</div>
</div>
</el-row>
</el-row>
</div>
<div
class=
"split"
></div>
<div
style=
"width: 50%"
>
<el-row
type=
"flex"
justify=
"space-around"
align=
"middle"
>
<div
class=
"metric-icon-box"
>
<i
class=
"el-icon-warning-outline fail"
></i>
<div
class=
"value"
>
{{
fail
}}
</div>
<div
class=
"name"
>
{{
$t
(
'
api_report.fail
'
)
}}
</div>
</div>
<div
class=
"metric-icon-box"
>
<i
class=
"el-icon-document-checked assertions"
></i>
<div
class=
"value"
>
{{
assertions
}}
</div>
<div
class=
"name"
>
{{
$t
(
'
api_report.assertions_pass
'
)
}}
</div>
</div>
<div
class=
"metric-icon-box"
>
<i
class=
"el-icon-document-copy total"
></i>
<div
class=
"value"
>
{{
this
.
content
.
total
}}
</div>
<div
class=
"name"
>
{{
$t
(
'
api_report.request
'
)
}}
</div>
</div>
</el-row>
</div>
</el-row>
</div>
</
template
>
<
script
>
import
MsChart
from
"
@/business/components/common/chart/MsChart
"
;
export
default
{
name
:
"
MsMetricChart
"
,
components
:
{
MsChart
},
props
:
{
content
:
Object
,
totalTime
:
Number
},
data
()
{
return
{
hour
:
0
,
minutes
:
0
,
seconds
:
0
,
time
:
0
,
}
},
created
()
{
this
.
initTime
()
},
methods
:
{
initTime
()
{
this
.
time
=
this
.
totalTime
this
.
seconds
=
Math
.
floor
(
this
.
time
/
1000
)
if
(
this
.
seconds
>=
1
)
{
if
(
this
.
seconds
>
60
)
{
this
.
minutes
=
Math
.
round
(
this
.
time
/
60
)
this
.
seconds
=
Math
.
round
(
this
.
time
%
60
)
this
.
time
=
this
.
minutes
+
"
min
"
+
this
.
seconds
+
"
s
"
}
if
(
this
.
seconds
>
60
)
{
this
.
minutes
=
Math
.
round
(
this
.
time
/
60
)
this
.
seconds
=
Math
.
round
(
this
.
time
%
60
)
this
.
time
=
this
.
minutes
+
"
min
"
+
this
.
seconds
+
"
s
"
}
if
(
this
.
minutes
>
60
)
{
this
.
hour
=
Math
.
round
(
this
.
minutes
/
60
)
this
.
minutes
=
Math
.
round
(
this
.
minutes
%
60
)
this
.
time
=
this
.
hour
+
"
hour
"
+
this
.
minutes
+
"
min
"
+
this
.
seconds
+
"
s
"
}
this
.
time
=
(
this
.
seconds
)
+
"
s
"
}
else
{
this
.
time
=
this
.
totalTime
+
"
ms
"
}
},
},
computed
:
{
options
()
{
return
{
color
:
[
'
#67C23A
'
,
'
#F56C6C
'
],
tooltip
:
{
trigger
:
'
item
'
,
formatter
:
'
{b}: {c} ({d}%)
'
},
title
:
[{
text
:
this
.
content
.
total
,
subtext
:
this
.
$t
(
'
api_report.request
'
),
top
:
'
center
'
,
left
:
'
center
'
,
textStyle
:
{
rich
:
{
align
:
'
center
'
,
value
:
{
fontSize
:
32
,
fontWeight
:
'
bold
'
,
padding
:
[
10
,
0
]
},
name
:
{
fontSize
:
14
,
fontWeight
:
'
normal
'
,
color
:
'
#7F7F7F
'
,
}
}
}
}],
series
:
[
{
type
:
'
pie
'
,
radius
:
[
'
80%
'
,
'
90%
'
],
avoidLabelOverlap
:
false
,
hoverAnimation
:
false
,
itemStyle
:
{
normal
:
{
borderColor
:
"
#FFF
"
,
shadowColor
:
'
#E1E1E1
'
,
shadowBlur
:
10
}
},
labelLine
:
{
show
:
false
},
data
:
[
{
value
:
this
.
content
.
success
},
{
value
:
this
.
content
.
error
},
]
}
]
};
},
fail
()
{
return
(
this
.
content
.
error
/
this
.
content
.
total
*
100
).
toFixed
(
0
)
+
"
%
"
;
},
assertions
()
{
return
this
.
content
.
passAssertions
+
"
/
"
+
this
.
content
.
totalAssertions
;
}
},
}
</
script
>
<
style
scoped
>
.metric-container
{
padding
:
20px
;
}
.metric-container
#chart
{
width
:
140px
;
height
:
140px
;
margin-right
:
40px
;
}
.metric-container
.split
{
margin
:
20px
;
height
:
100px
;
border-left
:
1px
solid
#D8DBE1
;
}
.metric-container
.circle
{
width
:
12px
;
height
:
12px
;
border-radius
:
50%
;
box-shadow
:
0
0
20px
1px
rgba
(
200
,
216
,
226
,
.42
);
display
:
inline-block
;
margin-right
:
10px
;
vertical-align
:
middle
;
}
.metric-container
.circle.success
{
background-color
:
#67C23A
;
}
.metric-container
.circle.fail
{
background-color
:
#F56C6C
;
}
.metric-box
{
display
:
inline-block
;
text-align
:
center
;
}
.metric-box
.value
{
font-size
:
32px
;
font-weight
:
600
;
letter-spacing
:
-.5px
;
}
.metric-time
.value
{
font-size
:
25px
;
font-weight
:
400
;
letter-spacing
:
-.5px
;
}
.metric-box
.name
{
font-size
:
16px
;
letter-spacing
:
-.2px
;
color
:
#404040
;
}
.metric-icon-box
{
text-align
:
center
;
margin
:
0
10px
;
}
.metric-icon-box
.value
{
font-size
:
20px
;
font-weight
:
600
;
letter-spacing
:
-.4px
;
line-height
:
28px
;
vertical-align
:
middle
;
}
.metric-icon-box
.name
{
font-size
:
13px
;
letter-spacing
:
1px
;
color
:
#BFBFBF
;
line-height
:
18px
;
}
.metric-icon-box
.fail
{
color
:
#F56C6C
;
font-size
:
40px
;
}
.metric-icon-box
.assertions
{
font-size
:
40px
;
}
.metric-icon-box
.total
{
font-size
:
40px
;
}
</
style
>
frontend/src/business/components/api/automation/report/components/RequestMetric.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<div
class=
"metric-container"
>
<el-row
type=
"flex"
>
<div
class=
"metric"
>
<div
class=
"value"
>
{{
request
.
responseResult
.
responseTime
}}
ms
</div>
<div
class=
"name"
>
{{
$t
(
'
api_report.response_time
'
)
}}
</div>
<br>
<div
class=
"value"
>
{{
request
.
responseResult
.
latency
}}
ms
</div>
<div
class=
"name"
>
{{
$t
(
'
api_report.latency
'
)
}}
</div>
</div>
<div
class=
"metric"
>
<div
class=
"value"
>
{{
request
.
requestSize
}}
bytes
</div>
<div
class=
"name"
>
{{
$t
(
'
api_report.request_size
'
)
}}
</div>
<br>
<div
class=
"value"
>
{{
request
.
responseResult
.
responseSize
}}
bytes
</div>
<div
class=
"name"
>
{{
$t
(
'
api_report.response_size
'
)
}}
</div>
</div>
<div
class=
"metric horizontal"
>
<el-row
type=
"flex"
>
<div
class=
"code"
>
<div
class=
"value"
:class=
"
{'error': error}">
{{
request
.
responseResult
.
responseCode
}}
</div>
<div
class=
"name"
>
{{
$t
(
'
api_report.response_code
'
)
}}
</div>
</div>
<div
class=
"split"
></div>
<div
class=
"message"
>
<div
class=
"value"
>
{{
request
.
responseResult
.
responseMessage
}}
</div>
<div
class=
"name"
>
{{
$t
(
'
api_report.response_message
'
)
}}
</div>
</div>
</el-row>
</div>
</el-row>
</div>
</
template
>
<
script
>
export
default
{
name
:
"
MsRequestMetric
"
,
props
:
{
request
:
Object
},
computed
:
{
error
()
{
return
this
.
request
.
responseResult
.
responseCode
>=
400
;
}
}
}
</
script
>
<
style
scoped
>
.metric-container
{
padding
:
20px
;
}
.metric
{
padding
:
20px
;
border
:
1px
solid
#EBEEF5
;
min-width
:
120px
;
height
:
114px
;
}
.metric
+
.metric
{
margin-left
:
20px
;
}
.metric
.value
{
font-size
:
16px
;
font-weight
:
500
;
word-break
:
break-all
;
}
.metric
.name
{
color
:
#404040
;
opacity
:
0.5
;
padding
:
5px
0
;
}
.metric.horizontal
{
width
:
100%
;
}
.metric
.code
{
min-width
:
120px
;
}
.metric
.code
.value
{
color
:
#67C23A
;
}
.metric
.code
.value.error
{
color
:
#F56C6C
;
}
.metric
.split
{
height
:
114px
;
border-left
:
1px
solid
#EBEEF5
;
margin-right
:
20px
;
}
.metric
.message
{
max-height
:
114px
;
overflow-y
:
auto
;
}
</
style
>
frontend/src/business/components/api/automation/report/components/RequestResult.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<div
class=
"request-result"
>
<p
class=
"el-divider--horizontal"
></p>
<div
@
click=
"active"
>
<el-row
:gutter=
"10"
type=
"flex"
align=
"middle"
class=
"info"
>
<el-col
:span=
"10"
v-if=
"indexNumber!=undefined"
>
<div
class=
"method"
>
<div
class=
"el-step__icon is-text ms-api-col"
v-if=
"indexNumber%2 ==0"
>
<div
class=
"el-step__icon-inner"
>
{{
indexNumber
+
1
}}
</div>
</div>
<div
class=
"el-step__icon is-text ms-api-col-create"
v-else
>
<div
class=
"el-step__icon-inner"
>
{{
indexNumber
+
1
}}
</div>
</div>
{{
request
.
name
}}
</div>
</el-col>
<el-col
:span=
"4"
>
<div
class=
"method"
>
{{
request
.
method
}}
</div>
</el-col>
<el-col
:span=
"5"
>
<el-tooltip
effect=
"dark"
:content=
"request.responseResult.responseCode"
placement=
"bottom"
:open-delay=
"800"
>
<div
class=
"url"
>
{{
request
.
responseResult
.
responseCode
}}
</div>
</el-tooltip>
</el-col>
<el-col
:span=
"3"
>
{{
request
.
responseResult
.
responseTime
}}
ms
</el-col>
<el-col
:span=
"2"
>
<div
class=
"success"
>
<el-tag
size=
"mini"
type=
"success"
v-if=
"request.success"
>
{{
$t
(
'
api_report.success
'
)
}}
</el-tag>
<el-tag
size=
"mini"
type=
"danger"
v-else
>
{{
$t
(
'
api_report.fail
'
)
}}
</el-tag>
</div>
</el-col>
</el-row>
</div>
</div>
</
template
>
<
script
>
import
MsRequestMetric
from
"
./RequestMetric
"
;
import
MsAssertionResults
from
"
./AssertionResults
"
;
import
MsRequestText
from
"
./RequestText
"
;
import
MsResponseText
from
"
./ResponseText
"
;
export
default
{
name
:
"
MsRequestResult
"
,
components
:
{
MsResponseText
,
MsRequestText
,
MsAssertionResults
,
MsRequestMetric
},
props
:
{
request
:
Object
,
scenarioName
:
String
,
indexNumber
:
Number
,
},
data
()
{
return
{}
},
methods
:
{
active
()
{
this
.
$emit
(
"
requestResult
"
,
{
request
:
this
.
request
,
scenarioName
:
this
.
scenarioName
});
}
},
}
</
script
>
<
style
scoped
>
.request-result
{
width
:
100%
;
min-height
:
40px
;
padding
:
2px
0
;
}
.request-result
.info
{
margin-left
:
20px
;
cursor
:
pointer
;
}
.request-result
.method
{
color
:
#1E90FF
;
font-size
:
14px
;
font-weight
:
500
;
line-height
:
40px
;
padding-left
:
5px
;
}
.request-result
.url
{
color
:
#7f7f7f
;
font-size
:
12px
;
font-weight
:
400
;
margin-top
:
4px
;
white-space
:
nowrap
;
text-overflow
:
ellipsis
;
overflow
:
hidden
;
word-break
:
break-all
;
}
.request-result
.tab
.el-tabs__header
{
margin
:
0
;
}
.request-result
.text
{
height
:
300px
;
overflow-y
:
auto
;
}
.sub-result
.info
{
background-color
:
#FFF
;
}
.sub-result
.method
{
border-left
:
5px
solid
#1E90FF
;
padding-left
:
20px
;
}
.sub-result
:last-child
{
border-bottom
:
1px
solid
#EBEEF5
;
}
.ms-api-col
{
background-color
:
#FCF1F1
;
border-color
:
#67C23A
;
margin-right
:
10px
;
color
:
#67C23A
;
}
.ms-api-col-create
{
background-color
:
#EBF2F2
;
border-color
:
#008080
;
margin-right
:
10px
;
color
:
#008080
;
}
.el-divider--horizontal
{
margin
:
2px
0
;
background
:
0
0
;
border-top
:
1px
solid
#e8eaec
;
}
</
style
>
frontend/src/business/components/api/automation/report/components/RequestResultTail.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<div
class=
"request-result"
>
<div
@
click=
"active"
>
<el-row
:gutter=
"10"
type=
"flex"
align=
"middle"
class=
"info"
>
<el-col
:span=
"12"
>
<i
class=
"icon el-icon-arrow-right"
:class=
"
{'is-active': isActive}"/>
{{
scenarioName
}}
</el-col>
<el-col
:span=
"4"
>
{{
$t
(
'
api_report.start_time
'
)
}}
</el-col>
<el-col
:span=
"2"
>
{{
$t
(
'
api_report.response_time
'
)
}}
</el-col>
<el-col
:span=
"2"
>
{{
$t
(
'
api_report.error
'
)
}}
</el-col>
<el-col
:span=
"2"
>
{{
$t
(
'
api_report.assertions
'
)
}}
</el-col>
<el-col
:span=
"2"
>
{{
$t
(
'
api_report.result
'
)
}}
</el-col>
</el-row>
<el-row
:gutter=
"10"
type=
"flex"
align=
"middle"
class=
"info"
>
<el-col
:span=
"2"
>
<div
class=
"method"
>
{{
request
.
method
}}
</div>
</el-col>
<el-col
:span=
"10"
>
<div
class=
"name"
>
{{
request
.
name
}}
</div>
<el-tooltip
effect=
"dark"
:content=
"request.url"
placement=
"bottom"
:open-delay=
"800"
>
<div
class=
"url"
>
{{
request
.
url
}}
</div>
</el-tooltip>
</el-col>
<el-col
:span=
"4"
>
{{
request
.
startTime
|
timestampFormatDate
(
true
)
}}
</el-col>
<el-col
:span=
"2"
>
<div
class=
"time"
>
{{
request
.
responseResult
.
responseTime
}}
</div>
</el-col>
<el-col
:span=
"2"
>
{{
request
.
error
}}
</el-col>
<el-col
:span=
"2"
>
{{
assertion
}}
</el-col>
<el-col
:span=
"2"
>
<el-tag
size=
"mini"
type=
"success"
v-if=
"request.success"
>
{{
$t
(
'
api_report.success
'
)
}}
</el-tag>
<el-tag
size=
"mini"
type=
"danger"
v-else
>
{{
$t
(
'
api_report.fail
'
)
}}
</el-tag>
</el-col>
</el-row>
</div>
<el-collapse-transition>
<div
v-show=
"isActive"
>
<el-tabs
v-model=
"activeName"
v-show=
"isActive"
v-if=
"hasSub"
>
<el-tab-pane
:label=
"$t('api_report.sub_result')"
name=
"sub"
>
<ms-request-result
class=
"sub-result"
v-for=
"(sub, index) in request.subRequestResults"
:key=
"index"
:request=
"sub"
/>
</el-tab-pane>
<el-tab-pane
:label=
"$t('api_report.request_result')"
name=
"result"
>
<ms-request-metric
:request=
"request"
/>
<ms-request-text
:request=
"request"
/>
<br>
<ms-response-text
:request-type=
"requestType"
:response=
"request.responseResult"
/>
</el-tab-pane>
</el-tabs>
<div
v-else
>
<ms-request-metric
:request=
"request"
/>
<ms-request-text
v-if=
"isCodeEditAlive"
:request=
"request"
/>
<br>
<ms-response-text
:request-type=
"requestType"
v-if=
"isCodeEditAlive"
:response=
"request.responseResult"
/>
</div>
</div>
</el-collapse-transition>
</div>
</
template
>
<
script
>
import
MsRequestMetric
from
"
./RequestMetric
"
;
import
MsAssertionResults
from
"
./AssertionResults
"
;
import
MsRequestText
from
"
./RequestText
"
;
import
MsResponseText
from
"
./ResponseText
"
;
import
MsRequestResult
from
"
./RequestResult
"
;
export
default
{
name
:
"
MsRequestResultTail
"
,
components
:
{
MsResponseText
,
MsRequestText
,
MsAssertionResults
,
MsRequestMetric
,
MsRequestResult
},
props
:
{
request
:
Object
,
scenarioName
:
String
,
requestType
:
String
},
data
()
{
return
{
isActive
:
true
,
activeName
:
"
sub
"
,
isCodeEditAlive
:
true
}
},
methods
:
{
active
()
{
this
.
isActive
=
!
this
.
isActive
;
},
reload
()
{
this
.
isCodeEditAlive
=
false
;
this
.
$nextTick
(()
=>
(
this
.
isCodeEditAlive
=
true
));
}
},
watch
:
{
'
request.responseResult
'
()
{
this
.
reload
();
}
},
computed
:
{
assertion
()
{
return
this
.
request
.
passAssertions
+
"
/
"
+
this
.
request
.
totalAssertions
;
},
hasSub
()
{
return
this
.
request
.
subRequestResults
.
length
>
0
;
},
}
}
</
script
>
<
style
scoped
>
.request-result
{
width
:
100%
;
min-height
:
40px
;
padding
:
2px
0
;
}
.request-result
.info
{
background-color
:
#F9F9F9
;
margin-left
:
20px
;
cursor
:
pointer
;
}
.request-result
.method
{
color
:
#1E90FF
;
font-size
:
14px
;
font-weight
:
500
;
line-height
:
40px
;
padding-left
:
5px
;
}
.request-result
.url
{
color
:
#7f7f7f
;
font-size
:
12px
;
font-weight
:
400
;
margin-top
:
4px
;
white-space
:
nowrap
;
text-overflow
:
ellipsis
;
overflow
:
hidden
;
word-break
:
break-all
;
}
.request-result
.tab
.el-tabs__header
{
margin
:
0
;
}
.request-result
.text
{
height
:
300px
;
overflow-y
:
auto
;
}
.sub-result
.info
{
background-color
:
#FFF
;
}
.sub-result
.method
{
border-left
:
5px
solid
#1E90FF
;
padding-left
:
20px
;
}
.sub-result
:last-child
{
border-bottom
:
1px
solid
#EBEEF5
;
}
.request-result
.icon.is-active
{
transform
:
rotate
(
90deg
);
}
</
style
>
frontend/src/business/components/api/automation/report/components/RequestText.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<div
class=
"text-container"
>
<div
@
click=
"active"
class=
"collapse"
>
<i
class=
"icon el-icon-arrow-right"
:class=
"
{'is-active': isActive}"/>
{{
$t
(
'
api_report.request
'
)
}}
</div>
<el-collapse-transition>
<el-tabs
v-model=
"activeName"
v-show=
"isActive"
>
<el-tab-pane
label=
"Body"
name=
"body"
class=
"pane"
>
<pre>
{{
request
.
body
}}
</pre>
</el-tab-pane>
<el-tab-pane
label=
"Headers"
name=
"headers"
class=
"pane"
>
<pre>
{{
request
.
headers
}}
</pre>
</el-tab-pane>
<el-tab-pane
label=
"Cookies"
name=
"cookies"
class=
"pane"
>
<pre>
{{
request
.
cookies
}}
</pre>
</el-tab-pane>
</el-tabs>
</el-collapse-transition>
</div>
</
template
>
<
script
>
export
default
{
name
:
"
MsRequestText
"
,
props
:
{
request
:
Object
},
data
()
{
return
{
isActive
:
true
,
activeName
:
"
body
"
,
}
},
methods
:
{
active
()
{
this
.
isActive
=
!
this
.
isActive
;
}
},
}
</
script
>
<
style
scoped
>
.text-container
.icon
{
padding
:
5px
;
}
.text-container
.collapse
{
cursor
:
pointer
;
}
.text-container
.collapse
:hover
{
opacity
:
0.8
;
}
.text-container
.icon.is-active
{
transform
:
rotate
(
90deg
);
}
.text-container
.pane
{
background-color
:
#F9F9F9
;
padding
:
10px
;
height
:
250px
;
overflow-y
:
auto
;
}
pre
{
margin
:
0
;
}
</
style
>
frontend/src/business/components/api/automation/report/components/ResponseText.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<div
class=
"text-container"
>
<div
@
click=
"active"
class=
"collapse"
>
<i
class=
"icon el-icon-arrow-right"
:class=
"
{'is-active': isActive}"/>
{{
$t
(
'
api_report.response
'
)
}}
</div>
<el-collapse-transition>
<el-tabs
v-model=
"activeName"
v-show=
"isActive"
>
<el-tab-pane
:class=
"'body-pane'"
label=
"Body"
name=
"body"
class=
"pane"
>
<ms-sql-result-table
v-if=
"isSqlType"
:body=
"response.body"
/>
<ms-code-edit
v-if=
"!isSqlType"
:mode=
"mode"
:read-only=
"true"
:data=
"response.body"
:modes=
"modes"
ref=
"codeEdit"
/>
</el-tab-pane>
<el-tab-pane
label=
"Headers"
name=
"headers"
class=
"pane"
>
<pre>
{{
response
.
headers
}}
</pre>
</el-tab-pane>
<el-tab-pane
:label=
"$t('api_report.assertions')"
name=
"assertions"
class=
"pane assertions"
>
<ms-assertion-results
:assertions=
"response.assertions"
/>
</el-tab-pane>
<el-tab-pane
:label=
"$t('api_test.request.extract.label')"
name=
"label"
class=
"pane"
>
<pre>
{{
response
.
vars
}}
</pre>
</el-tab-pane>
<el-tab-pane
v-if=
"activeName == 'body'"
:disabled=
"true"
name=
"mode"
class=
"pane assertions"
>
<template
v-slot:label
>
<ms-dropdown
v-if=
"!isSqlType"
:commands=
"modes"
:default-command=
"mode"
@
command=
"modeChange"
/>
<ms-dropdown
v-if=
"isSqlType"
:commands=
"sqlModes"
:default-command=
"mode"
@
command=
"sqlModeChange"
/>
</
template
>
</el-tab-pane>
</el-tabs>
</el-collapse-transition>
</div>
</template>
<
script
>
import
MsAssertionResults
from
"
./AssertionResults
"
;
import
MsCodeEdit
from
"
../../../../common/components/MsCodeEdit
"
;
import
MsDropdown
from
"
../../../../common/components/MsDropdown
"
;
import
{
BODY_FORMAT
,
RequestFactory
,
Request
,
SqlRequest
}
from
"
../../../definition/model/ApiTestModel
"
;
import
MsSqlResultTable
from
"
./SqlResultTable
"
;
export
default
{
name
:
"
MsResponseText
"
,
components
:
{
MsSqlResultTable
,
MsDropdown
,
MsCodeEdit
,
MsAssertionResults
,
},
props
:
{
requestType
:
String
,
response
:
Object
},
data
()
{
return
{
isActive
:
true
,
activeName
:
"
body
"
,
modes
:
[
'
text
'
,
'
json
'
,
'
xml
'
,
'
html
'
],
sqlModes
:
[
'
text
'
,
'
table
'
],
mode
:
BODY_FORMAT
.
TEXT
}
},
methods
:
{
active
()
{
this
.
isActive
=
!
this
.
isActive
;
},
modeChange
(
mode
)
{
this
.
mode
=
mode
;
},
sqlModeChange
(
mode
)
{
this
.
mode
=
mode
;
}
},
mounted
()
{
if
(
!
this
.
response
.
headers
)
{
return
;
}
if
(
this
.
response
.
headers
.
indexOf
(
"
Content-Type: application/json
"
)
>
0
)
{
this
.
mode
=
BODY_FORMAT
.
JSON
;
}
},
computed
:
{
isSqlType
()
{
return
(
this
.
requestType
===
RequestFactory
.
TYPES
.
SQL
&&
this
.
response
.
responseCode
===
'
200
'
);
}
}
}
</
script
>
<
style
scoped
>
.body-pane
{
padding
:
10px
!important
;
background
:
white
!important
;
}
.text-container
.icon
{
padding
:
5px
;
}
.text-container
.collapse
{
cursor
:
pointer
;
}
.text-container
.collapse
:hover
{
opacity
:
0.8
;
}
.text-container
.icon.is-active
{
transform
:
rotate
(
90deg
);
}
.text-container
.pane
{
background-color
:
#F5F5F5
;
padding
:
0
10px
;
height
:
250px
;
overflow-y
:
auto
;
}
.text-container
.pane.assertions
{
padding
:
0
;
}
pre
{
margin
:
0
;
}
</
style
>
frontend/src/business/components/api/automation/report/components/ScenarioResult.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<div
class=
"scenario-result"
>
<div
v-for=
"(request, index) in scenario.requestResults"
:key=
"index"
>
<ms-request-result
:key=
"index"
:request=
"request"
:indexNumber=
"index"
v-on:requestResult=
"requestResult"
:scenarioName=
"scenario.name"
/>
</div>
</div>
</
template
>
<
script
>
import
MsRequestResult
from
"
./RequestResult
"
;
export
default
{
name
:
"
MsScenarioResult
"
,
components
:
{
MsRequestResult
},
props
:
{
scenario
:
Object
,
},
data
()
{
return
{
isActive
:
false
,
}
},
methods
:
{
active
()
{
this
.
isActive
=
!
this
.
isActive
;
},
requestResult
(
requestResult
)
{
this
.
$emit
(
"
requestResult
"
,
requestResult
);
}
},
computed
:
{
assertion
()
{
return
this
.
scenario
.
passAssertions
+
"
/
"
+
this
.
scenario
.
totalAssertions
;
},
success
()
{
return
this
.
scenario
.
error
===
0
;
}
}
}
</
script
>
<
style
scoped
>
.scenario-result
{
width
:
100%
;
padding
:
2px
0
;
}
.scenario-result
+
.scenario-result
{
border-top
:
1px
solid
#DCDFE6
;
}
.scenario-result
.info
{
height
:
40px
;
cursor
:
pointer
;
}
.scenario-result
.icon
{
padding
:
5px
;
}
.scenario-result
.icon.is-active
{
transform
:
rotate
(
90deg
);
}
</
style
>
frontend/src/business/components/api/automation/report/components/ScenarioResults.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<el-card
class=
"scenario-results"
>
<ms-scenario-result
v-for=
"(scenario, index) in scenarios"
:key=
"index"
:scenario=
"scenario"
:indexNumber=
"index"
v-on:requestResult=
"requestResult"
/>
</el-card>
</
template
>
<
script
>
import
MsScenarioResult
from
"
./ScenarioResult
"
;
export
default
{
name
:
"
MsScenarioResults
"
,
components
:
{
MsScenarioResult
},
props
:
{
scenarios
:
Array
},
methods
:
{
requestResult
(
requestResult
)
{
this
.
$emit
(
"
requestResult
"
,
requestResult
);
}
}
}
</
script
>
<
style
scoped
>
.scenario-header
{
border
:
1px
solid
#EBEEF5
;
background-color
:
#F9FCFF
;
border-left
:
0
;
border-right
:
0
;
padding
:
5px
0
;
}
</
style
>
frontend/src/business/components/api/automation/report/components/SqlResultTable.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<div>
<el-table
v-for=
"(table, index) in tables"
:key=
"index"
:data=
"table.tableData"
border
size=
"mini"
highlight-current-row
>
<el-table-column
v-for=
"(title, index) in table.titles"
:key=
"index"
:label=
"title"
min-width=
"150px"
>
<template
v-slot:default=
"scope"
>
<el-popover
placement=
"top"
trigger=
"click"
>
<el-container>
<div>
{{
scope
.
row
[
title
]
}}
</div>
</el-container>
<span
class=
"table-content"
slot=
"reference"
>
{{
scope
.
row
[
title
]
}}
</span>
</el-popover>
</
template
>
</el-table-column>
</el-table>
</div>
</template>
<
script
>
export
default
{
name
:
"
MsSqlResultTable
"
,
data
()
{
return
{
tables
:
[],
titles
:
[]
}
},
props
:
{
body
:
String
},
created
()
{
if
(
!
this
.
body
)
{
return
;
}
let
rowArry
=
this
.
body
.
split
(
"
\n
"
);
this
.
getTableData
(
rowArry
);
if
(
this
.
tables
.
length
>
1
)
{
for
(
let
i
=
0
;
i
<
this
.
tables
.
length
;
i
++
)
{
if
(
this
.
tables
[
i
].
titles
.
length
===
1
&&
i
<
this
.
tables
.
length
-
1
)
{
this
.
tables
[
i
].
tableData
.
splice
(
this
.
tables
[
i
].
tableData
.
length
-
1
,
1
);
}
}
let
lastTable
=
this
.
tables
[
this
.
tables
.
length
-
1
];
if
(
lastTable
.
titles
.
length
===
1
)
{
if
(
lastTable
.
tableData
.
length
>
4
)
{
lastTable
.
tableData
.
splice
(
lastTable
.
tableData
.
length
-
4
,
4
);
}
else
{
this
.
tables
.
splice
(
this
.
tables
.
length
-
1
,
1
);
}
}
else
{
this
.
tables
.
splice
(
this
.
tables
.
length
-
1
,
1
);
}
}
else
{
let
table
=
this
.
tables
[
0
];
table
.
tableData
.
splice
(
table
.
tableData
.
length
-
4
,
4
);
}
},
methods
:
{
getTableData
(
rowArry
)
{
let
titles
;
let
result
=
[];
for
(
let
i
=
0
;
i
<
rowArry
.
length
;
i
++
)
{
let
colArray
=
rowArry
[
i
].
split
(
"
\t
"
);
if
(
i
===
0
)
{
titles
=
colArray
;
}
else
{
if
(
colArray
.
length
!=
titles
.
length
)
{
// 创建新的表
if
(
colArray
.
length
===
1
&&
colArray
[
0
]
===
''
)
{
this
.
getTableData
(
rowArry
.
slice
(
i
+
1
));
}
else
{
this
.
getTableData
(
rowArry
.
slice
(
i
));
}
break
;
}
else
{
let
item
=
{};
for
(
let
j
=
0
;
j
<
colArray
.
length
;
j
++
)
{
item
[
titles
[
j
]]
=
(
colArray
[
j
]
?
colArray
[
j
]
:
""
);
}
result
.
push
(
item
);
}
}
}
this
.
tables
.
splice
(
0
,
0
,
{
titles
:
titles
,
tableData
:
result
});
}
}
}
</
script
>
<
style
scoped
>
.el-table
{
margin-bottom
:
20px
;
}
.el-table
>>>
.cell
{
white-space
:
nowrap
;
}
.table-content
{
cursor
:
pointer
;
}
.el-container
{
overflow
:
auto
;
max-height
:
500px
;
}
</
style
>
frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue
浏览文件 @
1aab595a
...
...
@@ -64,6 +64,7 @@
import
MsTablePagination
from
"
@/business/components/common/pagination/TablePagination
"
;
import
ShowMoreBtn
from
"
@/business/components/track/case/components/ShowMoreBtn
"
;
import
MsTag
from
"
../../../common/components/MsTag
"
;
import
{
getUUID
}
from
"
@/common/js/utils
"
;
export
default
{
name
:
"
MsApiScenarioList
"
,
...
...
@@ -101,7 +102,7 @@
},
methods
:
{
search
()
{
this
.
condition
.
filters
=
[
"
Saved
"
,
"
Success
"
,
"
Fail
"
];
this
.
condition
.
filters
=
[
"
Prepare
"
,
"
Underway
"
,
"
Completed
"
];
if
(
this
.
currentModule
!=
null
)
{
if
(
this
.
currentModule
.
id
===
"
root
"
)
{
this
.
condition
.
moduleIds
=
[];
...
...
@@ -158,10 +159,11 @@
},
copy
(
row
)
{
row
.
id
=
getUUID
();
this
.
$emit
(
'
edit
'
,
row
);
},
remove
(
row
)
{
if
(
this
.
currentModule
!==
undefined
&&
this
.
currentModule
.
id
===
"
gc
"
)
{
if
(
this
.
currentModule
!==
undefined
&&
this
.
currentModule
!=
null
&&
this
.
currentModule
.
id
===
"
gc
"
)
{
this
.
$get
(
'
/api/automation/delete/
'
+
row
.
id
,
()
=>
{
this
.
$success
(
this
.
$t
(
'
commons.delete_success
'
));
this
.
search
();
...
...
frontend/src/business/components/api/automation/scenario/EditApiScenario.vue
浏览文件 @
1aab595a
...
...
@@ -132,9 +132,9 @@
</el-col>
<el-col
:span=
"8"
>
{{$t('api_test.definition.request.run_env')}}:
<el-select
v-model=
"current
Scenario.e
nvironmentId"
size=
"small"
class=
"ms-htt-width"
<el-select
v-model=
"current
E
nvironmentId"
size=
"small"
class=
"ms-htt-width"
:placeholder=
"$t('api_test.definition.request.run_env')"
@
change=
"environmentChange"
clearable
>
clearable
>
<el-option
v-for=
"(environment, index) in environments"
:key=
"index"
:label=
"environment.name + (environment.config.httpConfig.socket ? (': ' + environment.config.httpConfig.protocol + '://' + environment.config.httpConfig.socket) : '')"
:value=
"environment.id"
/>
...
...
@@ -237,8 +237,8 @@
<!--接口列表-->
<el-drawer
:visible.sync=
"apiListVisible"
:destroy-on-close=
"true"
direction=
"ltr"
:withHeader=
"false"
:title=
"$t('api_test.automation.api_list_import')"
:modal=
"false"
size=
"90%"
>
<ms-api-definition
:visible=
"true"
:currentRow=
"currentRow"
/>
<
el-button
style=
"float: right;margin: 20px"
type=
"primary"
@
click=
"copyApi('REF')"
>
{{$t('api_test.scenario.reference')}}
</el-button
>
<el-button
style=
"float: right;margin: 20px 0px 0px "
@
click=
"copyApi('Copy')"
>
{{ $t('commons.copy') }}
</el-button>
<
!--<el-button style="float: right;margin: 20px" type="primary" @click="copyApi('REF')">{{$t('api_test.scenario.reference')}}</el-button>--
>
<el-button
style=
"float: right;margin: 20px 0px 0px "
type=
"primary"
@
click=
"copyApi('Copy')"
>
{{ $t('commons.copy') }}
</el-button>
</el-drawer>
<!--自定义接口-->
...
...
@@ -257,8 +257,12 @@
<!--TAG-->
<ms-add-tag
@
refreshTags=
"refreshTags"
ref=
"tag"
/>
<!--执行组件-->
<ms-run
:debug=
"true"
:environment=
"currentEnvironment
"
:reportId=
"reportId"
:run-data=
"scenarioDefinition
"
<ms-run
:debug=
"true"
:environment=
"currentEnvironment
Id"
:reportId=
"reportId"
:run-data=
"debugData
"
@
runRefresh=
"runRefresh"
ref=
"runTest"
/>
<!-- 调试结果 -->
<el-drawer
:visible.sync=
"debugVisible"
:destroy-on-close=
"true"
direction=
"ltr"
:withHeader=
"false"
:title=
"$t('test_track.plan_view.test_result')"
:modal=
"false"
size=
"90%"
>
<ms-api-report-detail
:report-id=
"reportId"
/>
</el-drawer>
</div>
</el-card>
</template>
...
...
@@ -283,6 +287,8 @@
import
MsRun
from
"
./Run
"
;
import
MsImportApiScenario
from
"
./ImportApiScenario
"
;
import
MsApiScenarioComponent
from
"
./ApiScenarioComponent
"
;
import
MsApiReportDetail
from
"
../report/ApiReportDetail
"
;
export
default
{
name
:
"
EditApiScenario
"
,
...
...
@@ -291,7 +297,7 @@
currentProject
:
{},
currentScenario
:
{},
},
components
:
{
ApiEnvironmentConfig
,
MsAddTag
,
MsRun
,
MsApiScenarioComponent
,
MsImportApiScenario
,
MsJsr233Processor
,
MsConstantTimer
,
MsIfController
,
MsApiAssertions
,
MsApiExtract
,
MsApiDefinition
,
MsApiComponent
,
MsApiCustomize
},
components
:
{
ApiEnvironmentConfig
,
MsA
piReportDetail
,
MsA
ddTag
,
MsRun
,
MsApiScenarioComponent
,
MsImportApiScenario
,
MsJsr233Processor
,
MsConstantTimer
,
MsIfController
,
MsApiAssertions
,
MsApiExtract
,
MsApiDefinition
,
MsApiComponent
,
MsApiCustomize
},
data
()
{
return
{
props
:
{
...
...
@@ -309,7 +315,7 @@
},
environments
:
[],
tags
:
[],
currentEnvironment
:
{}
,
currentEnvironment
Id
:
""
,
maintainerOptions
:
[],
value
:
API_STATUS
[
0
].
id
,
options
:
API_STATUS
,
...
...
@@ -319,6 +325,7 @@
apiListVisible
:
false
,
customizeVisible
:
false
,
scenarioVisible
:
false
,
debugVisible
:
false
,
customizeRequest
:
{
protocol
:
"
HTTP
"
,
type
:
"
API
"
,
hashTree
:
[],
referenced
:
'
Created
'
,
active
:
false
},
operatingElements
:
[],
currentRow
:
{
cases
:
[],
apis
:
[]},
...
...
@@ -326,6 +333,7 @@
expandedNode
:
[],
scenarioDefinition
:
[],
path
:
"
/api/automation/create
"
,
debugData
:
[],
reportId
:
""
,
}
},
...
...
@@ -527,8 +535,14 @@
},
runDebug
()
{
/*触发执行操作*/
if
(
!
this
.
currentEnvironmentId
)
{
this
.
$error
(
this
.
$t
(
'
api_test.environment.select_environment
'
));
return
;
}
let
scenario
=
{
id
:
this
.
currentScenario
.
id
,
name
:
this
.
currentScenario
.
name
,
type
:
"
scenario
"
,
referenced
:
'
Created
'
,
environmentId
:
this
.
currentEnvironmentId
,
hashTree
:
this
.
scenarioDefinition
};
this
.
debugData
=
[];
this
.
debugData
.
push
(
scenario
);
this
.
reportId
=
getUUID
().
substring
(
0
,
8
);
//this.isReloadData = true;
},
getEnvironments
()
{
if
(
this
.
currentProject
)
{
...
...
@@ -547,14 +561,6 @@
}
this
.
$refs
.
environmentConfig
.
open
(
this
.
currentProject
.
id
);
},
environmentChange
(
value
)
{
for
(
let
i
in
this
.
environments
)
{
if
(
this
.
environments
[
i
].
id
===
value
)
{
this
.
currentEnvironment
=
this
.
environments
[
i
];
break
;
}
}
},
environmentConfigClose
()
{
this
.
getEnvironments
();
},
...
...
@@ -599,14 +605,18 @@
})
},
getApiScenario
()
{
if
(
this
.
currentScenario
.
tagId
!=
undefined
)
{
if
(
this
.
currentScenario
.
tagId
!=
undefined
&&
!
(
this
.
currentScenario
.
tagId
instanceof
Array
)
)
{
this
.
currentScenario
.
tagId
=
JSON
.
parse
(
this
.
currentScenario
.
tagId
);
}
if
(
this
.
currentScenario
.
id
)
{
this
.
path
=
"
/api/automation/update
"
;
this
.
result
=
this
.
$get
(
"
/api/automation/getApiScenario/
"
+
this
.
currentScenario
.
id
,
response
=>
{
if
(
response
.
data
)
{
this
.
scenarioDefinition
=
JSON
.
parse
(
response
.
data
.
scenarioDefinition
);
this
.
path
=
"
/api/automation/update
"
;
if
(
response
.
data
.
scenarioDefinition
!=
null
)
{
let
obj
=
JSON
.
parse
(
response
.
data
.
scenarioDefinition
);
this
.
currentEnvironmentId
=
obj
.
environmentId
;
this
.
scenarioDefinition
=
obj
.
hashTree
;
}
}
})
}
...
...
@@ -614,11 +624,13 @@
setParameter
()
{
this
.
currentScenario
.
projectId
=
this
.
currentProject
.
id
;
if
(
!
this
.
currentScenario
.
id
)
{
this
.
currentScenario
.
id
=
getUUID
()
.
substring
(
0
,
8
)
;
this
.
currentScenario
.
id
=
getUUID
();
}
this
.
currentScenario
.
stepTotal
=
this
.
scenarioDefinition
.
length
;
this
.
currentScenario
.
modulePath
=
this
.
getPath
(
this
.
currentScenario
.
apiScenarioModuleId
);
this
.
currentScenario
.
scenarioDefinition
=
JSON
.
stringify
(
this
.
scenarioDefinition
);
// 构建一个场景对象 方便引用处理
let
scenario
=
{
id
:
this
.
currentScenario
.
id
,
name
:
this
.
currentScenario
.
name
,
type
:
"
scenario
"
,
referenced
:
'
Created
'
,
environmentId
:
this
.
currentEnvironmentId
,
hashTree
:
this
.
scenarioDefinition
};
this
.
currentScenario
.
scenarioDefinition
=
JSON
.
stringify
(
scenario
);
this
.
currentScenario
.
tagId
=
JSON
.
stringify
(
this
.
currentScenario
.
tagId
);
if
(
this
.
currentModule
!=
null
)
{
this
.
currentScenario
.
modulePath
=
this
.
currentModule
.
method
!==
undefined
?
this
.
currentModule
.
method
:
null
;
...
...
@@ -626,6 +638,7 @@
}
},
runRefresh
()
{
this
.
debugVisible
=
true
;
this
.
isReloadData
=
false
;
}
}
...
...
frontend/src/business/components/api/automation/scenario/ImportApiScenario.vue
浏览文件 @
1aab595a
...
...
@@ -56,6 +56,7 @@
importApiScenario
()
{
let
scenarios
=
[];
if
(
this
.
currentScenario
)
{
console
.
log
(
this
.
currentScenario
)
this
.
currentScenario
.
forEach
(
item
=>
{
let
obj
=
{
id
:
item
.
id
,
name
:
item
.
name
,
type
:
"
scenario
"
,
referenced
:
'
REF
'
,
resourceId
:
getUUID
()};
scenarios
.
push
(
obj
);
...
...
frontend/src/business/components/api/automation/scenario/Run.vue
浏览文件 @
1aab595a
...
...
@@ -10,7 +10,7 @@
name
:
'
MsRun
'
,
components
:
{},
props
:
{
environment
:
Object
,
environment
:
String
,
debug
:
Boolean
,
reportId
:
String
,
runData
:
Array
,
...
...
@@ -96,26 +96,21 @@
},
run
()
{
let
testPlan
=
new
TestPlan
();
let
threadGroup
=
new
ThreadGroup
();
threadGroup
.
hashTree
=
[];
testPlan
.
hashTree
=
[
threadGroup
];
this
.
runData
.
forEach
(
item
=>
{
let
threadGroup
=
new
ThreadGroup
();
threadGroup
.
hashTree
=
[];
threadGroup
.
name
=
item
.
name
;
threadGroup
.
hashTree
.
push
(
item
);
testPlan
.
hashTree
.
push
(
threadGroup
);
})
let
reqObj
=
{
id
:
this
.
reportId
,
testElement
:
testPlan
};
console
.
log
(
"
====
"
,
testPlan
)
let
reqObj
=
{
id
:
this
.
reportId
,
reportId
:
this
.
reportId
,
environmentId
:
this
.
environment
,
testElement
:
testPlan
};
let
bodyFiles
=
this
.
getBodyUploadFiles
(
reqObj
);
let
url
=
""
;
if
(
this
.
debug
)
{
url
=
"
/api/automation/run/debug
"
;
}
else
{
reqObj
.
reportId
=
"
run
"
;
url
=
"
/api/definition/run
"
;
}
let
url
=
"
/api/automation/run
"
;
this
.
$fileUpload
(
url
,
null
,
bodyFiles
,
reqObj
,
response
=>
{
this
.
runId
=
response
.
data
;
this
.
getResult
();
},
erro
=>
{
this
.
$emit
(
'
runRefresh
'
,
{});
},
erro
=>
{
});
}
}
...
...
frontend/src/business/components/api/definition/components/jmeter/components/test-plan/index.js
浏览文件 @
1aab595a
...
...
@@ -18,7 +18,7 @@ export default class TestPlan extends HashTreeElement {
this
.
serializeThreadGroups
=
this
.
initBoolProp
(
'
TestPlan.serialize_threadgroups
'
,
false
);
this
.
tearDownOnShutdown
=
this
.
initBoolProp
(
'
TestPlan.tearDown_on_shutdown
'
,
true
);
this
.
userDefineClasspath
=
this
.
initStringProp
(
'
TestPlan.user_define_classpath
'
);
this
.
hashTree
=
[];
this
.
userDefinedVariables
=
[];
let
elementProp
=
this
.
initElementProp
(
'
TestPlan.user_defined_variables
'
,
'
Arguments
'
);
...
...
frontend/src/business/components/api/head/ApiHeaderMenus.vue
浏览文件 @
1aab595a
...
...
@@ -40,6 +40,11 @@
<el-divider
class=
"menu-divider"
/>
<ms-show-all
:index=
"'/api/report/list/all'"
/>
</el-submenu>
<el-menu-item
v-permission=
"['test_manager','test_user','test_viewer']"
:index=
"'/api/monitor/view'"
>
{{ $t('commons.monitor') }}
</el-menu-item>
</el-menu>
</el-col>
<el-col
:span=
"8"
>
...
...
frontend/src/business/components/api/monitor/ApiMonitor.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<ms-container>
<ms-aside-container>
<ms-api-monitor-search
@
getApiUrl=
"getApiUrl(arguments)"
@
getTodayData=
"getTodayData"
@
initPage=
"initPage(arguments)"
></ms-api-monitor-search>
</ms-aside-container>
<ms-main-container>
<div
class=
"menu-wrapper"
>
<div
class=
"menu-left"
>
<el-radio-group
id=
"date-radio"
v-model=
radioSelect
size=
"small"
>
<el-radio-button
:label=
"$t('api_monitor.today')"
@
click.native.prevent=
"getTodayData()"
/>
<el-radio-button
:label=
"$t('api_monitor.this_week')"
@
click.native.prevent=
"getWeekData()"
/>
<el-radio-button
:label=
"$t('api_monitor.this_mouth')"
@
click.native.prevent=
"getMouthData()"
/>
</el-radio-group>
<el-date-picker
id=
"date-picker"
v-model=
"datePicker"
:end-placeholder=
"$t('api_monitor.end_time')"
:range-separator=
"$t('api_monitor.to')"
:start-placeholder=
"$t('api_monitor.start_time')"
class=
"sales-view-date-picker"
size=
"small"
type=
"daterange"
unlink-panels
value-format=
"yyyy-MM-dd HH:mm:ss"
@
blur=
"getDatePicker"
/>
<el-tag
id=
"apiInfo"
type=
"info"
><a
id=
"api-url-title"
>
{{
apiUrl
}}
</a></el-tag>
</div>
</div>
<ms-api-monitor-chart
:api-url=
"apiUrl"
:rspCodeData=
this.rspCodeData
:rspCodexAxis=
this.rspCodexAxis
:rspTimeData=
this.rspTimeData
:rspTimexAxis=
this.rspTimexAxis
></ms-api-monitor-chart>
</ms-main-container>
</ms-container>
</
template
>
<
script
>
import
ApiMonitorCharts
from
'
@/business/components/api/monitor/ApiMonitorChart
'
;
import
MsApiMonitorChart
from
'
@/business/components/api/monitor/ApiMonitorChart
'
;
import
MsApiMonitorSearch
from
'
@/business/components/api/monitor/ApiMonitorSearch
'
;
import
MsMainContainer
from
'
@/business/components/common/components/MsMainContainer
'
;
import
MsAsideContainer
from
'
@/business/components/common/components/MsAsideContainer
'
;
import
MsContainer
from
'
@/business/components/common/components/MsContainer
'
;
import
{
formatTime
}
from
'
@/common/js/format-utils
'
;
export
default
{
name
:
'
MsApiMonitor
'
,
data
()
{
return
{
datePicker
:
null
,
rspTimeData
:
[],
rspTimexAxis
:
[],
rspCodeData
:
[],
rspCodexAxis
:
[],
apiUrl
:
''
,
radioSelect
:
this
.
$t
(
'
api_monitor.today
'
),
};
},
activated
()
{
this
.
initData
();
},
methods
:
{
initData
()
{
this
.
rspTimeData
=
[];
this
.
rspTimexAxis
=
[];
this
.
rspCodeData
=
[];
this
.
rspCodexAxis
=
[];
},
initPage
(
url
)
{
this
.
apiUrl
=
url
[
0
];
let
date1
=
new
Date
();
let
today1
=
formatTime
(
new
Date
(
date1
.
getFullYear
(),
date1
.
getMonth
(),
date1
.
getDate
()));
let
today2
=
formatTime
(
new
Date
(
date1
.
getFullYear
(),
date1
.
getMonth
(),
date1
.
getDate
()
+
1
));
this
.
initData
();
this
.
getResponseTime
(
this
.
apiUrl
,
today1
,
today2
);
this
.
getResponseCode
(
this
.
apiUrl
,
today1
,
today2
);
},
getResponseTime
(
apiUrl
,
startTime
,
endTime
)
{
return
this
.
$$get
(
'
/api/monitor/getResponseTime
'
,
{
'
startTime
'
:
startTime
,
'
endTime
'
:
endTime
},
{
'
apiUrl
'
:
apiUrl
},
response
=>
{
Object
.
values
(
response
.
data
).
forEach
(
value
=>
{
this
.
rspTimexAxis
.
push
(
value
.
startTime
);
this
.
rspTimeData
.
push
(
value
.
responseTime
);
});
});
},
getResponseCode
(
apiUrl
,
startTime
,
endTime
)
{
return
this
.
$$get
(
'
/api/monitor/getResponseCode
'
,
{
'
startTime
'
:
startTime
,
'
endTime
'
:
endTime
},
{
'
apiUrl
'
:
this
.
apiUrl
},
response
=>
{
Object
.
values
(
response
.
data
).
forEach
(
value
=>
{
this
.
rspCodexAxis
.
push
(
value
.
startTime
);
this
.
rspCodeData
.
push
(
value
.
responseCode
);
});
});
},
getDatePicker
()
{
this
.
initData
();
this
.
getResponseTime
(
this
.
apiUrl
,
this
.
datePicker
[
0
],
this
.
datePicker
[
1
]);
this
.
getResponseCode
(
this
.
apiUrl
,
this
.
datePicker
[
0
],
this
.
datePicker
[
1
]);
},
//获取周一日期
getFirstDayOfWeek
(
date
)
{
let
day
=
date
.
getDay
()
||
7
;
return
new
Date
(
date
.
getFullYear
(),
date
.
getMonth
(),
date
.
getDate
()
+
1
-
day
);
},
getApiUrl
(
payload
)
{
this
.
apiUrl
=
payload
[
0
];
},
//获取今日数据
getTodayData
()
{
this
.
radioSelect
=
this
.
$t
(
'
api_monitor.today
'
);
let
date1
=
new
Date
();
let
today1
=
formatTime
(
new
Date
(
date1
.
getFullYear
(),
date1
.
getMonth
(),
date1
.
getDate
()));
let
today2
=
formatTime
(
new
Date
(
date1
.
getFullYear
(),
date1
.
getMonth
(),
date1
.
getDate
()
+
1
));
this
.
initData
();
this
.
getResponseTime
(
this
.
apiUrl
,
today1
,
today2
);
this
.
getResponseCode
(
this
.
apiUrl
,
today1
,
today2
);
},
//获取周数据
getWeekData
()
{
this
.
radioSelect
=
this
.
$t
(
'
api_monitor.this_week
'
);
const
date1
=
new
Date
();
let
today
=
formatTime
(
date1
);
let
monday
=
formatTime
(
this
.
getFirstDayOfWeek
(
date1
));
this
.
initData
();
this
.
getResponseTime
(
this
.
apiUrl
,
monday
,
today
);
this
.
getResponseCode
(
this
.
apiUrl
,
monday
,
today
);
},
//获取月数据
getMouthData
()
{
this
.
radioSelect
=
this
.
$t
(
'
api_monitor.this_mouth
'
);
const
day
=
new
Date
(),
y
=
day
.
getFullYear
(),
m
=
day
.
getMonth
();
let
firstDay
=
formatTime
(
new
Date
(
y
,
m
,
1
));
let
today
=
formatTime
(
day
);
this
.
initData
();
this
.
getResponseTime
(
this
.
apiUrl
,
firstDay
,
today
);
this
.
getResponseCode
(
this
.
apiUrl
,
firstDay
,
today
);
},
},
components
:
{
MsApiMonitorChart
,
MsContainer
,
MsAsideContainer
,
MsMainContainer
,
MsApiMonitorSearch
,
ApiMonitorCharts
,
},
};
</
script
>
<
style
scoped
>
.menu-left
#apiInfo
{
margin-left
:
20px
;
}
#api-url-title
{
font-size
:
15px
;
}
</
style
>
frontend/src/business/components/api/monitor/ApiMonitorChart.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<div
class=
"monitor-view"
>
<div
class=
"response-time-view"
>
<el-card
shadow=
"hover"
>
<api-response-time-monitor-chart
:data=
"rspTimeData"
:xAxis=
"rspTimexAxis"
></api-response-time-monitor-chart>
</el-card>
</div>
<div
class=
"error-monitor-view"
>
<el-card
shadow=
"hover"
>
<api-error-monitor-chart
:api-url=
"apiUrl"
:data=
"rspCodeData"
:xAxis=
"rspCodexAxis"
></api-error-monitor-chart>
</el-card>
</div>
</div>
</
template
>
<
script
>
import
ApiResponseTimeMonitorChart
from
'
@/business/components/api/monitor/components/ApiResponseTimeMonitorChart
'
;
import
ApiErrorMonitorChart
from
'
@/business/components/api/monitor/components/ApiErrorMonitorChart
'
;
export
default
{
name
:
'
MsApiMonitorChart
'
,
props
:
[
'
rspTimeData
'
,
'
rspTimexAxis
'
,
'
rspCodeData
'
,
'
rspCodexAxis
'
,
'
apiUrl
'
],
data
()
{
return
{};
},
components
:
{
ApiErrorMonitorChart
,
ApiResponseTimeMonitorChart
,
},
methods
:
{}
};
</
script
>
<
style
scoped
>
.error-monitor-view
{
margin-top
:
20px
;
}
</
style
>
frontend/src/business/components/api/monitor/ApiMonitorSearch.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<div>
<el-input
v-model=
"input"
:placeholder=
"$t('api_monitor.please_search')"
prefix-icon=
"el-icon-search"
type=
"text"
@
input=
"searchAction"
></el-input>
<el-table
:data=
"searchResult"
:show-header=
"false"
border
class=
"question-tab2"
style=
"width: 100%"
@
row-click=
"getRowInfo"
>
<el-table-column
:label=
"$t('api_monitor.date')"
prop=
"url"
style=
"width: 100%"
>
</el-table-column>
</el-table>
</div>
</
template
>
<
script
>
function
throttle
(
fn
,
delay
)
{
let
t
=
null
,
begin
=
new
Date
().
getTime
();
return
function
()
{
const
_self
=
this
,
args
=
arguments
,
cur
=
new
Date
().
getTime
();
clearTimeout
(
t
);
if
(
cur
-
begin
>=
delay
)
{
fn
.
apply
(
_self
,
args
);
begin
=
cur
;
}
else
{
t
=
setTimeout
(
function
()
{
fn
.
apply
(
_self
,
args
);
},
delay
);
}
};
}
export
default
{
name
:
'
MsApiMonitorSearch
'
,
data
()
{
return
{
result
:
{},
searchResult
:
[],
items
:
[],
input
:
''
,
rowData
:
''
,
apiUrl
:
''
,
firstUrl
:
''
,
rspTimeData
:
[],
rspTimexAxis
:
[],
rspCodeData
:
[],
rspCodexAxis
:
[],
}
},
methods
:
{
searchAction
:
throttle
(
function
(
e
)
{
this
.
searchResult
=
this
.
items
.
filter
((
item
)
=>
{
if
(
item
.
url
.
includes
(
this
.
input
))
{
return
item
;
}
});
},
500
),
search
()
{
this
.
result
=
this
.
$get
(
'
/api/monitor/list
'
,
response
=>
{
this
.
items
=
response
.
data
;
});
},
getRowInfo
(
val
)
{
this
.
rowData
=
val
;
this
.
apiUrl
=
this
.
rowData
.
url
;
this
.
$emit
(
'
getApiUrl
'
,
this
.
apiUrl
,
this
.
firstUrl
);
this
.
$emit
(
'
getTodayData
'
);
},
},
activated
()
{
this
.
searchResult
=
[];
this
.
result
=
this
.
$get
(
'
/api/monitor/list
'
,
response
=>
{
if
(
response
.
data
.
length
!==
0
)
{
this
.
searchResult
=
response
.
data
;
this
.
firstUrl
=
response
.
data
[
0
].
url
;
this
.
$emit
(
'
initPage
'
,
this
.
firstUrl
);
this
.
search
();
}
});
}
}
</
script
>
<
style
scoped
>
</
style
>
frontend/src/business/components/api/monitor/components/ApiErrorMonitorChart.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<common-monitor-chart>
<template>
<div
id=
"response-time-chart"
:style=
"
{ width:'100%',height:'100%' }">
<chart
:options=
"getOptions()"
:style=
"
{ width:'100%' }">
</chart>
</div>
</
template
>
</common-monitor-chart>
</template>
<
script
>
import
CommonMonitorChart
from
'
@/business/components/api/monitor/components/CommonMonitorChart
'
;
export
default
{
name
:
'
ApiErrorMonitorChart
'
,
components
:
{
CommonMonitorChart
},
props
:
[
'
data
'
,
'
xAxis
'
,
'
apiUrl
'
,
],
data
()
{
return
{
reportId
:
''
,
};
},
methods
:
{
click
(
params
)
{
//如果状态不是以2开头
if
(
params
.
value
.
substr
(
0
,
1
)
!==
'
2
'
)
{
let
startTime
=
params
.
name
;
this
.
result
=
this
.
$$get
(
'
/api/monitor/getReportId
'
,
{
'
startTime
'
:
startTime
},
{
'
apiUrl
'
:
this
.
apiUrl
},
response
=>
{
this
.
reportId
=
response
.
data
;
let
reportId
=
this
.
reportId
let
url
=
'
#/api/report/view/
'
+
reportId
;
let
target
=
'
_blank
'
;
window
.
open
(
url
,
target
);
});
}
},
getOptions
()
{
return
{
title
:
{
text
:
'
HTTP状态码趋势
'
},
tooltip
:
{},
toolbox
:
{
show
:
true
,
feature
:
{
dataZoom
:
{
yAxisIndex
:
'
none
'
},
dataView
:
{
readOnly
:
false
},
restore
:
{},
saveAsImage
:
{}
}
},
dataZoom
:
[{
start
:
0
}],
xAxis
:
{
type
:
'
category
'
,
data
:
this
.
xAxis
},
yAxis
:
{
type
:
'
value
'
,
min
:
100
,
max
:
500
,
splitNumber
:
5
},
series
:
[{
type
:
'
line
'
,
smooth
:
true
,
data
:
this
.
data
,
lineStyle
:
{
color
:
'
#32CD32
'
},
itemStyle
:
{},
}],
};
},
},
}
</
script
>
<
style
scoped
>
</
style
>
frontend/src/business/components/api/monitor/components/ApiResponseTimeMonitorChart.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<common-monitor-chart>
<template>
<div
id=
"response-time-chart"
:style=
"
{ width:'100%',height:'100%' }">
<chart
:options=
"getOptions()"
:style=
"
{ width:'100%' }">
</chart>
</div>
</
template
>
</common-monitor-chart>
</template>
<
script
>
import
CommonMonitorChart
from
'
@/business/components/api/monitor/components/CommonMonitorChart
'
;
export
default
{
name
:
'
ApiResponseTimeMonitorChart
'
,
components
:
{
CommonMonitorChart
},
props
:
[
'
data
'
,
'
xAxis
'
],
methods
:
{
getOptions
()
{
return
{
title
:
{
text
:
'
响应时间趋势
'
},
tooltip
:
{},
toolbox
:
{
show
:
true
,
feature
:
{
dataZoom
:
{
yAxisIndex
:
'
none
'
},
dataView
:
{
readOnly
:
false
},
restore
:
{},
saveAsImage
:
{}
}
},
xAxis
:
{
type
:
'
category
'
,
data
:
this
.
xAxis
},
yAxis
:
{
},
dataZoom
:
[{
start
:
0
}],
series
:
[{
type
:
'
line
'
,
smooth
:
true
,
data
:
this
.
data
}],
};
}
},
};
</
script
>
<
style
scoped
>
</
style
>
frontend/src/business/components/api/monitor/components/CommonMonitorChart.vue
0 → 100644
浏览文件 @
1aab595a
<
template
>
<div
class=
"common-card"
>
<div
class=
"chart"
>
<slot></slot>
</div>
<div
class=
"line"
/>
</div>
</
template
>
<
script
>
export
default
{
props
:
{
title
:
String
,
}
};
</
script
>
<
style
scoped
>
.title
{
font-size
:
20px
;
color
:
#999
;
}
.line
{
margin
:
10px
0
;
border-top
:
1px
solid
#eee
;
}
</
style
>
<
style
>
</
style
>
frontend/src/business/components/api/report/components/ResponseText.vue
浏览文件 @
1aab595a
...
...
@@ -81,7 +81,7 @@ export default {
if
(
!
this
.
response
.
headers
)
{
return
;
}
if
(
this
.
response
.
headers
.
indexOf
(
"
Content-Type:
application/json
"
)
>
0
)
{
if
(
this
.
response
.
headers
.
indexOf
(
"
application/json
"
)
>
0
)
{
this
.
mode
=
BODY_FORMAT
.
JSON
;
}
},
...
...
frontend/src/business/components/api/report/components/SqlResultTable.vue
浏览文件 @
1aab595a
...
...
@@ -118,4 +118,12 @@
max-height
:
500px
;
}
.el-table
>>>
th
{
-webkit-user-select
:
text
;
-khtml-user-select
:
text
;
-moz-user-select
:
text
;
-ms-user-select
:
text
;
user-select
:
text
;
}
</
style
>
frontend/src/business/components/performance/report/components/PerformancePressureConfig.vue
浏览文件 @
1aab595a
...
...
@@ -226,8 +226,9 @@ export default {
if
(
handler
.
rampUpTime
<
handler
.
step
)
{
handler
.
step
=
handler
.
rampUpTime
;
}
let
color
=
[
'
#60acfc
'
,
'
#32d3eb
'
,
'
#5bc49f
'
,
'
#feb64d
'
,
'
#ff7c7c
'
,
'
#9287e7
'
,
'
#ca8622
'
,
'
#bda29a
'
,
'
#6e7074
'
,
'
#546570
'
,
'
#c4ccd3
'
];
handler
.
options
=
{
color
:
[
'
#60acfc
'
,
'
#32d3eb
'
,
'
#5bc49f
'
,
'
#feb64d
'
,
'
#ff7c7c
'
,
'
#9287e7
'
,
'
#ca8622
'
,
'
#bda29a
'
,
'
#6e7074
'
,
'
#546570
'
,
'
#c4ccd3
'
]
,
color
:
color
,
xAxis
:
{
type
:
'
category
'
,
boundaryGap
:
false
,
...
...
@@ -261,10 +262,10 @@ export default {
normal
:
{
color
:
new
echarts
.
graphic
.
LinearGradient
(
0
,
0
,
0
,
1
,
[{
offset
:
0
,
color
:
hexToRgba
(
handler
.
options
.
color
[
i
],
0.3
),
color
:
hexToRgba
(
color
[
i
%
color
.
length
],
0.3
),
},
{
offset
:
0.8
,
color
:
hexToRgba
(
handler
.
options
.
color
[
i
],
0
),
color
:
hexToRgba
(
color
[
i
%
color
.
length
],
0
),
}],
false
),
shadowColor
:
'
rgba(0, 0, 0, 0.1)
'
,
shadowBlur
:
10
...
...
@@ -272,7 +273,7 @@ export default {
},
itemStyle
:
{
normal
:
{
color
:
hexToRgb
(
handler
.
options
.
color
[
i
]),
color
:
hexToRgb
(
color
[
i
%
color
.
length
]),
borderColor
:
'
rgba(137,189,2,0.27)
'
,
borderWidth
:
12
}
...
...
frontend/src/business/components/performance/test/components/PerformancePressureConfig.vue
浏览文件 @
1aab595a
...
...
@@ -268,8 +268,9 @@ export default {
if
(
handler
.
rampUpTime
<
handler
.
step
)
{
handler
.
step
=
handler
.
rampUpTime
;
}
let
color
=
[
'
#60acfc
'
,
'
#32d3eb
'
,
'
#5bc49f
'
,
'
#feb64d
'
,
'
#ff7c7c
'
,
'
#9287e7
'
,
'
#ca8622
'
,
'
#bda29a
'
,
'
#6e7074
'
,
'
#546570
'
,
'
#c4ccd3
'
];
handler
.
options
=
{
color
:
[
'
#60acfc
'
,
'
#32d3eb
'
,
'
#5bc49f
'
,
'
#feb64d
'
,
'
#ff7c7c
'
,
'
#9287e7
'
,
'
#ca8622
'
,
'
#bda29a
'
,
'
#6e7074
'
,
'
#546570
'
,
'
#c4ccd3
'
]
,
color
:
color
,
xAxis
:
{
type
:
'
category
'
,
boundaryGap
:
false
,
...
...
@@ -302,10 +303,10 @@ export default {
normal
:
{
color
:
new
echarts
.
graphic
.
LinearGradient
(
0
,
0
,
0
,
1
,
[{
offset
:
0
,
color
:
hexToRgba
(
handler
.
options
.
color
[
i
],
0.3
),
color
:
hexToRgba
(
color
[
i
%
color
.
length
],
0.3
),
},
{
offset
:
0.8
,
color
:
hexToRgba
(
handler
.
options
.
color
[
i
],
0
),
color
:
hexToRgba
(
color
[
i
%
color
.
length
],
0
),
}],
false
),
shadowColor
:
'
rgba(0, 0, 0, 0.1)
'
,
shadowBlur
:
10
...
...
@@ -313,7 +314,7 @@ export default {
},
itemStyle
:
{
normal
:
{
color
:
hexToRgb
(
handler
.
options
.
color
[
i
]),
color
:
hexToRgb
(
color
[
i
%
color
.
length
]),
borderColor
:
'
rgba(137,189,2,0.27)
'
,
borderWidth
:
12
}
...
...
frontend/src/common/js/ajax.js
浏览文件 @
1aab595a
...
...
@@ -74,12 +74,12 @@ export default {
}
}
Vue
.
prototype
.
$$get
=
function
(
url
,
data
,
success
)
{
Vue
.
prototype
.
$$get
=
function
(
url
,
data
,
header
,
success
)
{
let
result
=
{
loading
:
true
};
if
(
!
success
)
{
return
axios
.
get
(
url
,
{
params
:
data
});
return
axios
.
get
(
url
,
{
params
:
data
,
headers
:
header
});
}
else
{
axios
.
get
(
url
,
{
params
:
data
}).
then
(
response
=>
{
axios
.
get
(
url
,
{
params
:
data
,
headers
:
header
}).
then
(
response
=>
{
then
(
success
,
response
,
result
);
}).
catch
(
error
=>
{
exception
(
error
,
result
,
url
);
...
...
frontend/src/common/js/chart.js
浏览文件 @
1aab595a
...
...
@@ -4,6 +4,8 @@ import 'echarts/lib/chart/bar'
import
'
echarts/lib/chart/pie
'
import
'
echarts/lib/component/tooltip
'
import
'
echarts/lib/component/title
'
import
'
echarts/lib/component/toolbox
'
;
import
'
echarts/lib/component/dataZoom
'
;
import
'
zrender/lib/svg/svg
'
export
default
{
...
...
frontend/src/common/js/format-utils.js
浏览文件 @
1aab595a
...
...
@@ -120,6 +120,42 @@ export function formatXml(text) {
return
outputText
.
replace
(
/
\s
+$/g
,
''
).
replace
(
/
\r
/g
,
'
\r\n
'
);
}
/**
* @param time 时间
* @param cFormat 格式
* @returns {string|null} 字符串
* @example formatTime('2018-1-29', '{y}/{m}/{d} {h}:{i}:{s}') // -> 2018/01/29 00:00:00
*/
export
function
formatTime
(
time
,
cFormat
)
{
if
(
arguments
.
length
===
0
)
return
null
;
if
((
time
+
''
).
length
===
10
)
{
time
=
+
time
*
1000
;
}
let
format
=
cFormat
||
'
{y}-{m}-{d} {h}:{i}:{s}
'
,
date
;
if
(
typeof
time
===
'
object
'
)
{
date
=
time
;
}
else
{
date
=
new
Date
(
time
);
}
let
formatObj
=
{
y
:
date
.
getFullYear
(),
m
:
date
.
getMonth
()
+
1
,
d
:
date
.
getDate
(),
h
:
date
.
getHours
(),
i
:
date
.
getMinutes
(),
s
:
date
.
getSeconds
(),
a
:
date
.
getDay
()
};
return
format
.
replace
(
/{
([
ymdhisa
])
+}/g
,
(
result
,
key
)
=>
{
let
value
=
formatObj
[
key
];
if
(
key
===
'
a
'
)
return
[
'
一
'
,
'
二
'
,
'
三
'
,
'
四
'
,
'
五
'
,
'
六
'
,
'
日
'
][
value
-
1
];
if
(
result
.
length
>
0
&&
value
<
10
)
{
value
=
'
0
'
+
value
;
}
return
value
||
0
;
});
}
function
getPrefix
(
prefixIndex
)
{
var
span
=
'
'
;
var
output
=
[];
...
...
frontend/src/i18n/en-US.js
浏览文件 @
1aab595a
...
...
@@ -162,7 +162,8 @@ export default {
between
:
"
Between
"
,
current_user
:
"
Current user
"
}
}
},
monitor
:
"
monitor
"
},
license
:
{
title
:
'
Authorization management
'
,
...
...
@@ -777,6 +778,16 @@ export default {
running
:
"
The test is reporting
"
,
not_exist
:
"
Test report does not exist
"
,
},
api_monitor
:
{
to
:
"
to
"
,
start_time
:
"
Start Time
"
,
end_time
:
"
End Time
"
,
today
:
"
Today
"
,
this_week
:
"
This Week
"
,
this_mouth
:
"
This Mouth
"
,
please_search
:
"
Please Search
"
,
date
:
"
Date
"
},
test_track
:
{
test_track
:
"
Track
"
,
confirm
:
"
Confirm
"
,
...
...
frontend/src/i18n/zh-CN.js
浏览文件 @
1aab595a
...
...
@@ -162,7 +162,8 @@ export default {
between
:
"
之间
"
,
current_user
:
"
是当前用户
"
}
}
},
monitor
:
"
监控
"
},
license
:
{
title
:
'
授权管理
'
,
...
...
@@ -807,6 +808,16 @@ export default {
running
:
"
测试报告导出中
"
,
not_exist
:
"
测试报告不存在
"
,
},
api_monitor
:
{
to
:
"
至
"
,
start_time
:
"
开始日期
"
,
end_time
:
"
结束日期
"
,
today
:
"
今日
"
,
this_week
:
"
本周
"
,
this_mouth
:
"
本月
"
,
please_search
:
"
请搜索
"
,
date
:
"
日期
"
},
test_track
:
{
test_track
:
"
测试跟踪
"
,
confirm
:
"
确 定
"
,
...
...
frontend/src/i18n/zh-TW.js
浏览文件 @
1aab595a
...
...
@@ -162,7 +162,8 @@ export default {
between
:
"
之間
"
,
current_user
:
"
是當前用戶
"
}
}
},
monitor
:
"
監控
"
},
license
:
{
title
:
'
授權管理
'
,
...
...
@@ -780,6 +781,16 @@ export default {
running
:
"
測試報告導出中
"
,
not_exist
:
"
測試報告不存在
"
,
},
api_monitor
:
{
to
:
"
到
"
,
start_time
:
"
開始時間
"
,
end_time
:
"
結束時間
"
,
today
:
"
今天
"
,
this_week
:
"
本週
"
,
this_mouth
:
"
本月
"
,
please_search
:
"
請搜索
"
,
date
:
"
日期
"
},
test_track
:
{
test_track
:
"
測試跟蹤
"
,
confirm
:
"
確 定
"
,
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录