Merge branch 'dev' of https://github.com/fit2cloudrd/metersphere-server into dev
Conflicts: frontend/src/i18n/en-US.js
This commit is contained in:
commit
a690ffecde
|
@ -43,9 +43,9 @@ public class APIReportController {
|
|||
return PageUtils.setPageInfo(page, apiReportService.list(request));
|
||||
}
|
||||
|
||||
@GetMapping("/get/{testId}")
|
||||
public ApiTestReport get(@PathVariable String testId) {
|
||||
return apiReportService.get(testId);
|
||||
@GetMapping("/get/{reportId}")
|
||||
public APIReportResult get(@PathVariable String reportId) {
|
||||
return apiReportService.get(reportId);
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
|
|
|
@ -9,5 +9,7 @@ import lombok.Setter;
|
|||
@Getter
|
||||
public class APIReportResult extends ApiTestReport {
|
||||
|
||||
private String testName;
|
||||
|
||||
private String projectName;
|
||||
}
|
||||
|
|
|
@ -93,7 +93,8 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
|
|||
}
|
||||
|
||||
RequestResult requestResult = getRequestResult(result);
|
||||
scenarioResult.getRequestResult().add(requestResult);
|
||||
scenarioResult.getRequestResults().add(requestResult);
|
||||
scenarioResult.addResponseTime(result.getTime());
|
||||
|
||||
testResult.addPassAssertions(requestResult.getPassAssertions());
|
||||
testResult.addTotalAssertions(requestResult.getTotalAssertions());
|
||||
|
@ -110,14 +111,18 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
|
|||
}
|
||||
|
||||
private RequestResult getRequestResult(SampleResult result) {
|
||||
String body = result.getSamplerData();
|
||||
String method = StringUtils.substringBefore(body, " ");
|
||||
|
||||
RequestResult requestResult = new RequestResult();
|
||||
requestResult.setName(result.getSampleLabel());
|
||||
requestResult.setUrl(result.getUrlAsString());
|
||||
requestResult.setSuccess(result.isSuccessful());
|
||||
requestResult.setBody(result.getSamplerData());
|
||||
requestResult.setMethod(method);
|
||||
requestResult.setBody(body);
|
||||
requestResult.setHeaders(result.getRequestHeaders());
|
||||
requestResult.setRequestSize(result.getSentBytes());
|
||||
requestResult.setTotalAssertions(result.getAssertionResults().length);
|
||||
requestResult.setSuccess(result.isSuccessful());
|
||||
|
||||
ResponseResult responseResult = requestResult.getResponseResult();
|
||||
responseResult.setBody(result.getResponseDataAsString());
|
||||
|
|
|
@ -1,21 +1,31 @@
|
|||
package io.metersphere.api.jmeter;
|
||||
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.config.JmeterProperties;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import org.apache.jmeter.save.SaveService;
|
||||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Service
|
||||
public class JMeterService {
|
||||
|
||||
@Resource
|
||||
private JmeterProperties jmeterProperties;
|
||||
|
||||
public void run(InputStream is) {
|
||||
JMeterUtils.loadJMeterProperties("/Users/q4speed/Downloads/apache-jmeter-5.2.1/bin/jmeter.properties");
|
||||
JMeterUtils.setJMeterHome("/Users/q4speed/Downloads/apache-jmeter-5.2.1");
|
||||
String JMETER_HOME = jmeterProperties.getHome();
|
||||
String JMETER_PROPERTIES = JMETER_HOME + "/bin/jmeter.properties";
|
||||
JMeterUtils.loadJMeterProperties(JMETER_PROPERTIES);
|
||||
JMeterUtils.setJMeterHome(JMETER_HOME);
|
||||
try {
|
||||
Object scriptWrapper = SaveService.loadElement(is);
|
||||
HashTree testPlan = getHashTree(scriptWrapper);
|
||||
|
|
|
@ -9,6 +9,8 @@ public class RequestResult {
|
|||
|
||||
private String url;
|
||||
|
||||
private String method;
|
||||
|
||||
private long requestSize;
|
||||
|
||||
private boolean success;
|
||||
|
|
|
@ -22,7 +22,11 @@ public class ScenarioResult {
|
|||
|
||||
private int passAssertions = 0;
|
||||
|
||||
private final List<RequestResult> requestResult = new ArrayList<>();
|
||||
private final List<RequestResult> requestResults = new ArrayList<>();
|
||||
|
||||
public void addResponseTime(long time) {
|
||||
this.responseTime += time;
|
||||
}
|
||||
|
||||
public void addError() {
|
||||
this.error++;
|
||||
|
|
|
@ -38,8 +38,8 @@ public class APIReportService {
|
|||
return extApiTestReportMapper.list(request);
|
||||
}
|
||||
|
||||
public ApiTestReport get(String id) {
|
||||
return apiTestReportMapper.selectByPrimaryKey(id);
|
||||
public APIReportResult get(String reportId) {
|
||||
return extApiTestReportMapper.get(reportId);
|
||||
}
|
||||
|
||||
public List<APIReportResult> listByTestId(String testId) {
|
||||
|
|
|
@ -13,4 +13,6 @@ public interface ExtApiTestReportMapper {
|
|||
|
||||
List<APIReportResult> listByTestId(@Param("testId") String testId);
|
||||
|
||||
APIReportResult get(@Param("id") String id);
|
||||
|
||||
}
|
||||
|
|
|
@ -4,12 +4,13 @@
|
|||
|
||||
<resultMap id="BaseResultMap" type="io.metersphere.api.dto.APIReportResult"
|
||||
extends="io.metersphere.base.mapper.ApiTestReportMapper.BaseResultMap">
|
||||
<result column="test_name" property="testName"/>
|
||||
<result column="project_name" property="projectName"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="list" resultMap="BaseResultMap">
|
||||
SELECT t.name, t.description,
|
||||
r.id, r.test_id, r.create_time, r.update_time, r.status,
|
||||
SELECT t.name AS test_name,
|
||||
r.name, r.description, r.id, r.test_id, r.create_time, r.update_time, r.status,
|
||||
project.name AS project_name
|
||||
FROM api_test_report r JOIN api_test t ON r.test_id = t.id
|
||||
JOIN project ON project.id = t.project_id
|
||||
|
@ -30,8 +31,8 @@
|
|||
</select>
|
||||
|
||||
<select id="listByTestId" resultMap="BaseResultMap">
|
||||
SELECT t.name, t.description,
|
||||
r.id, r.test_id, r.create_time, r.update_time, r.status,
|
||||
SELECT c,
|
||||
r.name, r.description, r.id, r.test_id, r.create_time, r.update_time, r.status,
|
||||
project.name AS project_name
|
||||
FROM api_test_report r JOIN api_test t ON r.test_id = t.id
|
||||
JOIN project ON project.id = t.project_id
|
||||
|
@ -41,4 +42,14 @@
|
|||
ORDER BY r.update_time DESC
|
||||
</select>
|
||||
|
||||
<select id="get" resultMap="BaseResultMap">
|
||||
SELECT r.*, t.name AS test_name, project.name AS project_name
|
||||
FROM api_test_report r JOIN api_test t ON r.test_id = t.id
|
||||
JOIN project ON project.id = t.project_id
|
||||
<where>
|
||||
r.id = #{id}
|
||||
</where>
|
||||
ORDER BY r.update_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
|
@ -1,20 +1,19 @@
|
|||
package io.metersphere.config;
|
||||
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties(prefix = JmeterProperties.JMETER_PREFIX)
|
||||
@Setter
|
||||
@Getter
|
||||
public class JmeterProperties {
|
||||
|
||||
public static final String JMETER_PREFIX = "jmeter";
|
||||
|
||||
private String image;
|
||||
|
||||
public String getImage() {
|
||||
return image;
|
||||
}
|
||||
private String home;
|
||||
|
||||
public void setImage(String image) {
|
||||
this.image = image;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
package io.metersphere.config;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties(prefix = KafkaProperties.KAFKA_PREFIX)
|
||||
@Getter
|
||||
@Setter
|
||||
public class KafkaProperties {
|
||||
public static final String KAFKA_PREFIX = "kafka";
|
||||
|
||||
|
@ -22,112 +26,8 @@ public class KafkaProperties {
|
|||
private KafkaProperties.Ssl ssl = new KafkaProperties.Ssl();
|
||||
private KafkaProperties.Log log = new KafkaProperties.Log();
|
||||
|
||||
public String getAcks() {
|
||||
return acks;
|
||||
}
|
||||
|
||||
public void setAcks(String acks) {
|
||||
this.acks = acks;
|
||||
}
|
||||
|
||||
public String getTopic() {
|
||||
return topic;
|
||||
}
|
||||
|
||||
public void setTopic(String topic) {
|
||||
this.topic = topic;
|
||||
}
|
||||
|
||||
public String getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void setFields(String fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public String getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(String timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getBootstrapServers() {
|
||||
return bootstrapServers;
|
||||
}
|
||||
|
||||
public void setBootstrapServers(String bootstrapServers) {
|
||||
this.bootstrapServers = bootstrapServers;
|
||||
}
|
||||
|
||||
public String getSampleFilter() {
|
||||
return sampleFilter;
|
||||
}
|
||||
|
||||
public void setSampleFilter(String sampleFilter) {
|
||||
this.sampleFilter = sampleFilter;
|
||||
}
|
||||
|
||||
public String getTestMode() {
|
||||
return testMode;
|
||||
}
|
||||
|
||||
public void setTestMode(String testMode) {
|
||||
this.testMode = testMode;
|
||||
}
|
||||
|
||||
|
||||
public String getCompressionType() {
|
||||
return compressionType;
|
||||
}
|
||||
|
||||
public void setCompressionType(String compressionType) {
|
||||
this.compressionType = compressionType;
|
||||
}
|
||||
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getParseAllReqHeaders() {
|
||||
return parseAllReqHeaders;
|
||||
}
|
||||
|
||||
public void setParseAllReqHeaders(String parseAllReqHeaders) {
|
||||
this.parseAllReqHeaders = parseAllReqHeaders;
|
||||
}
|
||||
|
||||
public String getParseAllResHeaders() {
|
||||
return parseAllResHeaders;
|
||||
}
|
||||
|
||||
public void setParseAllResHeaders(String parseAllResHeaders) {
|
||||
this.parseAllResHeaders = parseAllResHeaders;
|
||||
}
|
||||
|
||||
public String getBatchSize() {
|
||||
return batchSize;
|
||||
}
|
||||
|
||||
public void setBatchSize(String batchSize) {
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
public String getConnectionsMaxIdleMs() {
|
||||
return connectionsMaxIdleMs;
|
||||
}
|
||||
|
||||
public void setConnectionsMaxIdleMs(String connectionsMaxIdleMs) {
|
||||
this.connectionsMaxIdleMs = connectionsMaxIdleMs;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Ssl {
|
||||
private String enabled = "false";
|
||||
private String keyPassword;
|
||||
|
@ -140,124 +40,11 @@ public class KafkaProperties {
|
|||
private String protocol;
|
||||
private String enabledProtocols;
|
||||
private String provider;
|
||||
|
||||
public Ssl() {
|
||||
}
|
||||
|
||||
public String getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(String enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getKeyPassword() {
|
||||
return keyPassword;
|
||||
}
|
||||
|
||||
public void setKeyPassword(String keyPassword) {
|
||||
this.keyPassword = keyPassword;
|
||||
}
|
||||
|
||||
public String getKeystoreLocation() {
|
||||
return keystoreLocation;
|
||||
}
|
||||
|
||||
public void setKeystoreLocation(String keystoreLocation) {
|
||||
this.keystoreLocation = keystoreLocation;
|
||||
}
|
||||
|
||||
public String getKeystorePassword() {
|
||||
return keystorePassword;
|
||||
}
|
||||
|
||||
public void setKeystorePassword(String keystorePassword) {
|
||||
this.keystorePassword = keystorePassword;
|
||||
}
|
||||
|
||||
public String getKeystoreType() {
|
||||
return keystoreType;
|
||||
}
|
||||
|
||||
public void setKeystoreType(String keystoreType) {
|
||||
this.keystoreType = keystoreType;
|
||||
}
|
||||
|
||||
public String getTruststoreLocation() {
|
||||
return truststoreLocation;
|
||||
}
|
||||
|
||||
public void setTruststoreLocation(String truststoreLocation) {
|
||||
this.truststoreLocation = truststoreLocation;
|
||||
}
|
||||
|
||||
public String getTruststorePassword() {
|
||||
return truststorePassword;
|
||||
}
|
||||
|
||||
public void setTruststorePassword(String truststorePassword) {
|
||||
this.truststorePassword = truststorePassword;
|
||||
}
|
||||
|
||||
public String getTruststoreType() {
|
||||
return truststoreType;
|
||||
}
|
||||
|
||||
public void setTruststoreType(String truststoreType) {
|
||||
this.truststoreType = truststoreType;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public void setProtocol(String protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public String getEnabledProtocols() {
|
||||
return enabledProtocols;
|
||||
}
|
||||
|
||||
public void setEnabledProtocols(String enabledProtocols) {
|
||||
this.enabledProtocols = enabledProtocols;
|
||||
}
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public void setProvider(String provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
}
|
||||
|
||||
public Ssl getSsl() {
|
||||
return ssl;
|
||||
}
|
||||
|
||||
public void setSsl(Ssl ssl) {
|
||||
this.ssl = ssl;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Log {
|
||||
private String topic;
|
||||
|
||||
public String getTopic() {
|
||||
return topic;
|
||||
}
|
||||
|
||||
public void setTopic(String topic) {
|
||||
this.topic = topic;
|
||||
}
|
||||
}
|
||||
|
||||
public Log getLog() {
|
||||
return log;
|
||||
}
|
||||
|
||||
public void setLog(Log log) {
|
||||
this.log = log;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.github.pagehelper.Page;
|
|||
import com.github.pagehelper.PageHelper;
|
||||
import io.metersphere.base.domain.User;
|
||||
import io.metersphere.commons.constants.RoleConstants;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.PageUtils;
|
||||
import io.metersphere.commons.utils.Pager;
|
||||
import io.metersphere.controller.request.UserRequest;
|
||||
|
@ -17,6 +18,7 @@ import io.metersphere.service.UserService;
|
|||
import io.metersphere.service.WorkspaceService;
|
||||
import io.metersphere.user.SessionUser;
|
||||
import io.metersphere.user.SessionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
@ -120,6 +122,9 @@ public class UserController {
|
|||
|
||||
@PostMapping("/update/current")
|
||||
public UserDTO updateCurrentUser(@RequestBody User user) {
|
||||
UserDTO userDTO = userService.getUserDTO(user.getId());
|
||||
BeanUtils.copyProperties(user, userDTO);
|
||||
SessionUtils.putUser(SessionUser.fromUser(userDTO));
|
||||
userService.updateUser(user);
|
||||
return SessionUtils.getUser();
|
||||
}
|
||||
|
@ -182,6 +187,10 @@ public class UserController {
|
|||
@RequiresRoles(value = {RoleConstants.TEST_MANAGER,RoleConstants.ORG_ADMIN}, logical = Logical.OR)
|
||||
public void deleteMember(@PathVariable String workspaceId, @PathVariable String userId) {
|
||||
workspaceService.checkWorkspaceOwner(workspaceId);
|
||||
String currentUserId = SessionUtils.getUser().getId();
|
||||
if (StringUtils.equals(userId, currentUserId)) {
|
||||
MSException.throwException("Insufficient permissions!");
|
||||
}
|
||||
userService.deleteMember(workspaceId, userId);
|
||||
}
|
||||
|
||||
|
@ -202,6 +211,10 @@ public class UserController {
|
|||
@RequiresRoles(RoleConstants.ORG_ADMIN)
|
||||
public void delOrganizationMember(@PathVariable String organizationId, @PathVariable String userId) {
|
||||
organizationService.checkOrgOwner(organizationId);
|
||||
String currentUserId = SessionUtils.getUser().getId();
|
||||
if (StringUtils.equals(userId, currentUserId)) {
|
||||
MSException.throwException("Insufficient permissions!");
|
||||
}
|
||||
userService.delOrganizationMember(organizationId, userId);
|
||||
}
|
||||
|
||||
|
|
|
@ -124,11 +124,8 @@ public class UserService {
|
|||
}
|
||||
|
||||
public void updateUser(User user) {
|
||||
UserDTO userDTO = getUserDTO(user.getId());
|
||||
BeanUtils.copyProperties(user, userDTO);
|
||||
// MD5
|
||||
user.setPassword(CodingUtil.md5(user.getPassword()));
|
||||
SessionUtils.putUser(SessionUser.fromUser(userDTO));
|
||||
user.setUpdateTime(System.currentTimeMillis());
|
||||
userMapper.updateByPrimaryKeySelective(user);
|
||||
}
|
||||
|
|
|
@ -64,3 +64,4 @@ kafka.ssl.truststore-type=
|
|||
|
||||
# jmeter
|
||||
jmeter.image=registry.fit2cloud.com/metersphere/jmeter-master:0.0.4
|
||||
jmeter.home=/opt/jmeter/apache-jmeter-5.2.1
|
|
@ -23,8 +23,8 @@
|
|||
<el-divider/>
|
||||
<ms-show-all :index="'/api/test/list/all'"/>
|
||||
<ms-create-button :index="'/api/test/create'" :title="$t('load_test.create')"/>
|
||||
<!-- <el-menu-item :index="testCaseProjectPath" class="blank_item"></el-menu-item>-->
|
||||
<!-- <el-menu-item :index="testEditPath" class="blank_item"></el-menu-item>-->
|
||||
<!-- <el-menu-item :index="testCaseProjectPath" class="blank_item"></el-menu-item>-->
|
||||
<!-- <el-menu-item :index="testEditPath" class="blank_item"></el-menu-item>-->
|
||||
</el-submenu>
|
||||
|
||||
<el-submenu v-if="isCurrentWorkspaceUser"
|
||||
|
@ -33,7 +33,7 @@
|
|||
<ms-recent-list :options="reportRecent"/>
|
||||
<el-divider/>
|
||||
<ms-show-all :index="'/api/report/list/all'"/>
|
||||
<!-- <el-menu-item :index="reportViewPath" class="blank_item"></el-menu-item>-->
|
||||
<!-- <el-menu-item :index="reportViewPath" class="blank_item"></el-menu-item>-->
|
||||
</el-submenu>
|
||||
</el-menu>
|
||||
</el-col>
|
||||
|
@ -90,7 +90,7 @@
|
|||
title: this.$t('report.recent'),
|
||||
url: "/api/report/recent/5",
|
||||
index: function (item) {
|
||||
return '/api/report/view?id=' + item.id;
|
||||
return '/api/report/view/' + item.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,38 @@
|
|||
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="status"
|
||||
:label="$t('commons.status')">
|
||||
<template v-slot:default="{row}">
|
||||
<el-tag size="mini" type="info" v-if="row.status === 'Saved'">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="primary" v-else-if="row.status === 'Starting'">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="success" v-else-if="row.status === 'Running'">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="warning" v-else-if="row.status === 'Reporting'">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="info" v-else-if="row.status === 'Completed'">
|
||||
{{ 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>
|
||||
<span v-else>
|
||||
{{ row.status }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
width="150"
|
||||
:label="$t('commons.operating')">
|
||||
|
@ -101,7 +133,7 @@
|
|||
})
|
||||
},
|
||||
handleDelete(report) {
|
||||
this.$alert(this.$t('load_test.delete_confirm') + report.name + "?", '', {
|
||||
this.$alert(this.$t('api_report.delete_confirm') + report.name + "?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
|
|
|
@ -1,117 +1,113 @@
|
|||
<template>
|
||||
<div v-loading="result.loading" class="report-view-container">
|
||||
<div class="container" v-loading="result.loading">
|
||||
<div class="main-content">
|
||||
<el-card>
|
||||
<el-row>
|
||||
<section class="report-container">
|
||||
<header class="report-header" v-if="this.report.testId">
|
||||
<span>{{report.projectName}} / </span>
|
||||
<router-link :to="path">{{report.testName}}</router-link>
|
||||
</header>
|
||||
<main>
|
||||
<div class="scenario-chart">
|
||||
<ms-metric-chart :content="content"></ms-metric-chart>
|
||||
</div>
|
||||
<el-card>
|
||||
<div class="scenario-header">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="16">
|
||||
<el-row>
|
||||
<el-breadcrumb separator-class="el-icon-arrow-right">
|
||||
<el-breadcrumb-item :to="{ path: '/' }">{{report.projectName}}</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>{{report.testName}}</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>{{report.name}}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</el-row>
|
||||
<el-row class="ms-report-view-btns">
|
||||
<el-button type="primary" plain size="mini">立即停止</el-button>
|
||||
<el-button type="success" plain size="mini">再次执行</el-button>
|
||||
<el-button type="info" plain size="mini">导出</el-button>
|
||||
<el-button type="warning" plain size="mini">比较</el-button>
|
||||
</el-row>
|
||||
{{$t('api_report.scenario_name')}}
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<span class="ms-report-time-desc">
|
||||
持续时间: 30 分钟
|
||||
</span>
|
||||
<span class="ms-report-time-desc">
|
||||
开始时间: 2020-3-10 12:00:00
|
||||
</span>
|
||||
<span class="ms-report-time-desc">
|
||||
结束时间: 2020-3-10 12:30:00
|
||||
</span>
|
||||
<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-divider></el-divider>
|
||||
|
||||
<el-tabs v-model="active" type="border-card" :stretch="true">
|
||||
<el-tab-pane :label="$t('report.test_details')">
|
||||
<result-details />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('report.test_log_details')">
|
||||
<ms-report-log-details />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
</div>
|
||||
<ms-scenario-result v-for="(scenario, index) in content.scenarios" :key="index" :scenario="scenario"/>
|
||||
</el-card>
|
||||
</main>
|
||||
</section>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsReportLogDetails from './components/LogDetails';
|
||||
import ResultDetails from './components/ResultDetails';
|
||||
|
||||
import MsRequestResult from "./components/RequestResult";
|
||||
import MsScenarioResult from "./components/ScenarioResult";
|
||||
import MsMetricChart from "./components/MetricChart";
|
||||
|
||||
export default {
|
||||
name: "ApiReportView",
|
||||
components: {
|
||||
MsReportLogDetails,
|
||||
ResultDetails
|
||||
},
|
||||
name: "MsApiReportView",
|
||||
components: {MsMetricChart, MsScenarioResult, MsRequestResult},
|
||||
data() {
|
||||
return {
|
||||
content: {},
|
||||
report: {},
|
||||
result: {},
|
||||
active: '0',
|
||||
videoPath: '',
|
||||
report: {}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
initBreadcrumb() {
|
||||
if(this.reportId){
|
||||
this.result = this.$get("/api/report/test/pro/info/" + this.reportId, res => {
|
||||
let data = res.data;
|
||||
if(data){
|
||||
this.report = data;
|
||||
this.report.content = JSON.parse(this.report.content);
|
||||
}
|
||||
getReport() {
|
||||
if (this.reportId) {
|
||||
let url = "/api/report/get/" + this.reportId;
|
||||
this.result = this.$get(url, response => {
|
||||
this.report = response.data || {};
|
||||
this.content = JSON.parse(this.report.content);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initBreadcrumb();
|
||||
|
||||
watch: {
|
||||
'$route': 'getReport',
|
||||
},
|
||||
|
||||
created() {
|
||||
this.getReport();
|
||||
},
|
||||
|
||||
computed: {
|
||||
reportId: function () {
|
||||
return this.$route.params.reportId;
|
||||
},
|
||||
path() {
|
||||
return "/api/test/edit?id=" + this.report.testId;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.report-view-container {
|
||||
float: none;
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
.report-container {
|
||||
height: calc(100vh - 150px);
|
||||
min-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.report-view-container .main-content {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
.report-header {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.ms-report-view-btns {
|
||||
margin-top: 15px;
|
||||
.report-header a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ms-report-time-desc {
|
||||
text-align: left;
|
||||
display: block;
|
||||
color: #5C7878;
|
||||
.scenario-header {
|
||||
border: 1px solid #EBEEF5;
|
||||
background-color: #F9FCFF;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
padding: 5px 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<el-table :data="assertions" style="width: 100%">
|
||||
<el-table-column prop="name" :label="$t('api_report.assertions_name')" width="300">
|
||||
</el-table-column>
|
||||
<el-table-column prop="message" :label="$t('api_report.assertions_message')">
|
||||
</el-table-column>
|
||||
<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
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,202 @@
|
|||
<template>
|
||||
<div class="metric-container">
|
||||
<el-row type="flex" align="middle">
|
||||
<div style="width: 50%">
|
||||
<el-row type="flex" justify="center" align="middle">
|
||||
<chart id="chart" ref="chart" :options="options" :autoresize="true"></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>
|
||||
export default {
|
||||
name: "MsMetricChart",
|
||||
|
||||
props: {
|
||||
content: Object
|
||||
},
|
||||
|
||||
computed: {
|
||||
options() {
|
||||
return {
|
||||
color: ['#67C23A', '#F56C6C'],
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c} ({d}%)'
|
||||
},
|
||||
title: [{
|
||||
text: '{value|' + this.content.total + '}\n{name|' + 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, name: this.$t('api_report.success')},
|
||||
{value: this.content.error, name: this.$t('api_report.fail')},
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
fail() {
|
||||
return (this.content.error / this.content.total).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-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>
|
|
@ -0,0 +1,101 @@
|
|||
<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 {
|
||||
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;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,122 @@
|
|||
<template>
|
||||
<div class="request-result">
|
||||
<div @click="active">
|
||||
<el-row :gutter="10" type="flex" align="middle" class="info">
|
||||
<el-col :span="4">
|
||||
<div class="method">
|
||||
{{request.method}}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="name">{{request.name}}</div>
|
||||
<div class="url">{{request.url}}</div>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<div class="time">
|
||||
{{request.responseResult.responseTime}}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
{{error}}
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
{{request.passAssertions}} / {{request.totalAssertions}}
|
||||
</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">
|
||||
<ms-request-metric :request="request"/>
|
||||
<ms-request-text :request="request"/>
|
||||
<br>
|
||||
<ms-response-text :response="request.responseResult"/>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</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
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: false,
|
||||
activeName: "request",
|
||||
activeName2: "body",
|
||||
activeName3: "body",
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
error() {
|
||||
return this.request.totalAssertions - this.request.passAssertions;
|
||||
}
|
||||
}
|
||||
}
|
||||
</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 {
|
||||
/*border-left: 5px solid #1E90FF;*/
|
||||
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;
|
||||
overflow: auto;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.request-result .tab .el-tabs__header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.request-result .text {
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,73 @@
|
|||
<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: false,
|
||||
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>
|
|
@ -0,0 +1,77 @@
|
|||
<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 label="Body" name="body" class="pane">
|
||||
<div>{{response.body}}</div>
|
||||
</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">
|
||||
<ms-assertion-results :assertions="response.assertions"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsAssertionResults from "./AssertionResults";
|
||||
|
||||
export default {
|
||||
name: "MsResponseText",
|
||||
|
||||
components: {MsAssertionResults},
|
||||
|
||||
props: {
|
||||
response: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: false,
|
||||
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: #F5F5F5;
|
||||
padding: 0 10px;
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,94 @@
|
|||
<template>
|
||||
<div class="scenario-result">
|
||||
<div @click="active">
|
||||
<el-row :gutter="10" type="flex" align="middle" class="info">
|
||||
<el-col :span="16">
|
||||
<i class="icon el-icon-arrow-right" :class="{'is-active': isActive}"/>
|
||||
{{scenario.name}}
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
{{scenario.responseTime}}
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
{{scenario.error}}
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
{{assertion}}
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<el-tag size="mini" type="success" v-if="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">
|
||||
<ms-request-result v-for="(request, index) in scenario.requestResults" :key="index" :request="request"/>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</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;
|
||||
}
|
||||
},
|
||||
|
||||
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 #EBEEF5;
|
||||
}
|
||||
|
||||
.scenario-result .info {
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scenario-result .icon {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.scenario-result .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
</style>
|
|
@ -38,12 +38,43 @@
|
|||
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="status"
|
||||
:label="$t('commons.status')">
|
||||
<template v-slot:default="{row}">
|
||||
<el-tag size="mini" type="info" v-if="row.status === 'Saved'">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="primary" v-else-if="row.status === 'Starting'">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="success" v-else-if="row.status === 'Running'">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="warning" v-else-if="row.status === 'Reporting'">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="info" v-else-if="row.status === 'Completed'">
|
||||
{{ 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>
|
||||
<span v-else>
|
||||
{{ row.status }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
width="150"
|
||||
:label="$t('commons.operating')">
|
||||
<template v-slot:default="scope">
|
||||
<el-button @click="handleEdit(scope.row)" type="primary" icon="el-icon-edit" size="mini" circle/>
|
||||
<el-button @click="handleDelete(scope.row)" type="danger" icon="el-icon-delete" size="mini" circle/>
|
||||
<ms-table-operator @editClick="handleEdit(scope.row)" @deleteClick="handleDelete(scope.row)"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
@ -57,9 +88,10 @@
|
|||
<script>
|
||||
import MsTablePagination from "../../common/pagination/TablePagination";
|
||||
import MsTableHeader from "../../common/components/MsTableHeader";
|
||||
import MsTableOperator from "../../common/components/MsTableOperator";
|
||||
|
||||
export default {
|
||||
components: {MsTableHeader, MsTablePagination},
|
||||
components: {MsTableHeader, MsTablePagination, MsTableOperator},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
|
|
|
@ -78,8 +78,7 @@
|
|||
width="150"
|
||||
:label="$t('commons.operating')">
|
||||
<template v-slot:default="scope">
|
||||
<el-button @click="handleEdit(scope.row)" type="primary" icon="el-icon-edit" size="mini" circle/>
|
||||
<el-button @click="handleDelete(scope.row)" type="danger" icon="el-icon-delete" size="mini" circle/>
|
||||
<ms-table-operator @editClick="handleEdit(scope.row)" @deleteClick="handleDelete(scope.row)"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
@ -92,9 +91,10 @@
|
|||
|
||||
<script>
|
||||
import MsTablePagination from "../../common/pagination/TablePagination";
|
||||
import MsTableOperator from "../../common/components/MsTableOperator";
|
||||
|
||||
export default {
|
||||
components: {MsTablePagination},
|
||||
components: {MsTablePagination, MsTableOperator},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
<el-row type="flex" justify="start">
|
||||
<el-col :span="8">
|
||||
<h3>{{$t('load_test.domain_bind')}}</h3>
|
||||
<el-button icon="el-icon-circle-plus-outline" plain size="mini" @click="add('domains')">{{$t('commons.add')}}</el-button>
|
||||
<el-button icon="el-icon-circle-plus-outline" plain size="mini" @click="add('domains')">{{$t('commons.add')}}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- -->
|
||||
<el-row>
|
||||
<el-col :span="20">
|
||||
<el-col :span="24">
|
||||
<el-table :data="domains" size="mini" class="tb-edit" align="center" border highlight-current-row>
|
||||
<el-table-column
|
||||
align="center"
|
||||
|
@ -25,7 +26,7 @@
|
|||
:placeholder="$t('load_test.domain')"
|
||||
clearable>
|
||||
</el-input>
|
||||
<span >{{row.domain}}</span>
|
||||
<span>{{row.domain}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
|
@ -63,19 +64,8 @@
|
|||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('load_test.operating')">
|
||||
<template v-slot:default="{row, $index}">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
circle size="mini"
|
||||
@click="add('domains')">
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="mini"
|
||||
icon="el-icon-delete"
|
||||
circle
|
||||
@click="del(row, 'domains', $index)">
|
||||
</el-button>
|
||||
<ms-table-operator-button :tip="$t('commons.delete')" icon="el-icon-delete" type="danger"
|
||||
@exec="del(row, 'domains', $index)"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
@ -85,12 +75,13 @@
|
|||
<el-row>
|
||||
<el-col :span="8">
|
||||
<h3>{{$t('load_test.params')}}</h3>
|
||||
<el-button icon="el-icon-circle-plus-outline" plain size="mini" @click="add('params')">{{$t('commons.add')}}</el-button>
|
||||
<el-button icon="el-icon-circle-plus-outline" plain size="mini" @click="add('params')">{{$t('commons.add')}}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- -->
|
||||
<el-row>
|
||||
<el-col :span="20">
|
||||
<el-col :span="24">
|
||||
<el-table :data="params" size="mini" class="tb-edit" align="center" border highlight-current-row>
|
||||
<el-table-column
|
||||
align="center"
|
||||
|
@ -107,7 +98,7 @@
|
|||
:placeholder="$t('load_test.param_name')"
|
||||
clearable>
|
||||
</el-input>
|
||||
<span >{{row.name}}</span>
|
||||
<span>{{row.name}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
|
@ -146,20 +137,8 @@
|
|||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('load_test.operating')">
|
||||
<template v-slot:default="{row, $index}">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="mini"
|
||||
icon="el-icon-plus"
|
||||
circle
|
||||
@click="add(row)">
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="mini"
|
||||
icon="el-icon-delete"
|
||||
circle
|
||||
@click="del(row, 'params', $index)">
|
||||
</el-button>
|
||||
<ms-table-operator-button :tip="$t('commons.delete')" icon="el-icon-delete" type="danger"
|
||||
@exec="del(row, 'params', $index)"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
@ -198,8 +177,11 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import MsTableOperatorButton from "../../../common/components/MsTableOperatorButton";
|
||||
|
||||
export default {
|
||||
name: "PerformanceAdvancedConfig",
|
||||
components: {MsTableOperatorButton},
|
||||
data() {
|
||||
return {
|
||||
timeout: 100,
|
||||
|
@ -233,21 +215,6 @@
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
revertObject(row) {
|
||||
Object.keys(row).forEach(function (key) {
|
||||
row[key] = row[key + 'Origin'];
|
||||
});
|
||||
},
|
||||
saveOriginObject(row) {
|
||||
Object.keys(row).forEach(function (key) {
|
||||
row[key + 'Origin'] = row[key];
|
||||
});
|
||||
},
|
||||
delOriginObject(row) {
|
||||
Object.keys(row).forEach(function (key) {
|
||||
delete row[key + 'Origin'];
|
||||
});
|
||||
},
|
||||
getAdvancedConfig(testId) {
|
||||
this.$get('/performance/get-advanced-config/' + testId, (response) => {
|
||||
if (response.data) {
|
||||
|
@ -257,7 +224,6 @@
|
|||
this.statusCodeStr = this.statusCode.join(',');
|
||||
this.domains = data.domains || [];
|
||||
this.params = data.params || [];
|
||||
this.add('domains');
|
||||
/*this.domains.forEach(d => d.edit = false);
|
||||
this.params.forEach(d => d.edit = false);*/
|
||||
}
|
||||
|
@ -267,7 +233,7 @@
|
|||
if (dataName === 'domains') {
|
||||
this[dataName].push({
|
||||
domain: 'fit2cloud.com',
|
||||
enable: false,
|
||||
enable: true,
|
||||
ip: '127.0.0.1',
|
||||
edit: true,
|
||||
});
|
||||
|
@ -282,21 +248,14 @@
|
|||
}
|
||||
},
|
||||
edit(row) {
|
||||
this.saveOriginObject(row);
|
||||
row.edit = !row.edit
|
||||
},
|
||||
del(row, dataName, index) {
|
||||
this[dataName].splice(index, 1);
|
||||
},
|
||||
cancelEdit(row) {
|
||||
row.edit = false;
|
||||
// rollback changes
|
||||
this.revertObject(row);
|
||||
},
|
||||
confirmEdit(row) {
|
||||
row.edit = false;
|
||||
row.enable=true,
|
||||
this.saveOriginObject(row);
|
||||
row.enable = true;
|
||||
},
|
||||
groupBy(data, key) {
|
||||
return data.reduce((p, c) => {
|
||||
|
@ -343,8 +302,6 @@
|
|||
this.params.forEach(d => d.edit = false);
|
||||
},
|
||||
configurations() {
|
||||
this.domains.forEach(d => this.delOriginObject(d));
|
||||
this.params.forEach(d => this.delOriginObject(d));
|
||||
let statusCode = [];
|
||||
if (this.statusCodeStr) {
|
||||
statusCode = this.statusCodeStr.split(',');
|
||||
|
@ -368,20 +325,25 @@
|
|||
.edit-input {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.tb-edit .el-textarea {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tb-edit .current-row .el-textarea {
|
||||
display: block;
|
||||
}
|
||||
.tb-edit .current-row .el-textarea+span {
|
||||
|
||||
.tb-edit .current-row .el-textarea + span {
|
||||
display: none;
|
||||
}
|
||||
.el-col{
|
||||
|
||||
.el-col {
|
||||
text-align: left;
|
||||
}
|
||||
.el-col .el-table{
|
||||
align:center;
|
||||
|
||||
.el-col .el-table {
|
||||
align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -175,14 +175,11 @@
|
|||
cancelButtonText: this.$t('commons.cancel'),
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.loading = true;
|
||||
this.$get('/user/ws/member/delete/' + this.currentUser().lastWorkspaceId + '/' + row.id).then(() => {
|
||||
this.initTableData();
|
||||
this.loading = false;
|
||||
});
|
||||
this.result = this.$get('/user/ws/member/delete/' + this.currentUser().lastWorkspaceId + '/' + row.id,() => {
|
||||
this.$success(this.$t('commons.delete_success'));
|
||||
this.initTableData();
|
||||
});
|
||||
}).catch(() => {
|
||||
this.loading = false;
|
||||
this.$info(this.$t('commons.delete_cancel'));
|
||||
});
|
||||
},
|
||||
|
|
|
@ -96,6 +96,7 @@ export default {
|
|||
'special_characters_are_not_supported': 'Special characters are not supported',
|
||||
'mobile_number_format_is_incorrect': 'Mobile number format is incorrect',
|
||||
'email_format_is_incorrect': 'Email format is incorrect',
|
||||
'password_format_is_incorrect': 'Password format is incorrect (At least 8-16 characters, at least 1 uppercase letter, 1 lowercase letter and 1 number)',
|
||||
'password_format_is_incorrect':'Password format is incorrect (At least 8-16 characters, at least 1 uppercase letter, 1 lowercase letter and 1 number)',
|
||||
'old_password':'Old Password',
|
||||
'new_password':'New Password',
|
||||
|
@ -106,7 +107,7 @@ export default {
|
|||
'input_name': 'Please enter a user name',
|
||||
'input_id': 'Please enter a ID',
|
||||
'input_email': 'Please enter a email',
|
||||
'input_password':'Please enter a password',
|
||||
'input_password': 'Please enter a password',
|
||||
'special_characters_are_not_supported': 'Special characters are not supported',
|
||||
'mobile_number_format_is_incorrect': 'Mobile number format is incorrect',
|
||||
'email_format_is_incorrect': 'Email format is incorrect',
|
||||
|
@ -135,8 +136,8 @@ export default {
|
|||
'being_generated': 'Report is being generated...',
|
||||
},
|
||||
load_test: {
|
||||
'operating':'Operating',
|
||||
'pressure_prediction_chart':'Pressure Prediction Chart',
|
||||
'operating': 'Operating',
|
||||
'pressure_prediction_chart': 'Pressure Prediction Chart',
|
||||
'recent': 'Recent Tests',
|
||||
'search_by_name': 'Search by name',
|
||||
'project_name': 'Project',
|
||||
|
@ -192,7 +193,77 @@ export default {
|
|||
'download_log_file': 'Download',
|
||||
},
|
||||
api_test: {
|
||||
'select_resource_pool': 'Please select resource pool'
|
||||
save_and_run: "Save and Run",
|
||||
run: "Run",
|
||||
running: "Running",
|
||||
reset: "Rest",
|
||||
input_name: "Please enter the test name",
|
||||
select_project: "Please select project",
|
||||
scenario: {
|
||||
config: "Scenario Config",
|
||||
input_name: "Please enter the scenario name",
|
||||
name: "Scenario Name",
|
||||
base_url: "Base URL",
|
||||
base_url_description: "Base URL as URL prefix for all requests",
|
||||
parameters: "arguments",
|
||||
headers: "Headers",
|
||||
kv_description: "Will be used for requests where the item is not set",
|
||||
},
|
||||
request: {
|
||||
input_name: "Please enter the request name",
|
||||
name: "Request Name",
|
||||
method: "Method",
|
||||
url: "url",
|
||||
url_description: "etc: https://fit2cloud.com",
|
||||
parameters: "arguments",
|
||||
parameters_desc: "Parameters will be appended to the URL e.g. https://fit2cloud.com?Name=Value&Name2=Value2",
|
||||
headers: "Headers",
|
||||
body: "Body",
|
||||
body_kv: "Key Value",
|
||||
body_text: "Raw",
|
||||
assertions: {
|
||||
label: "Assertion",
|
||||
text: "Text",
|
||||
regex: "Regex",
|
||||
response_time: "Response Time",
|
||||
select_type: "Please select a request type",
|
||||
select_subject: "Please select a request subject",
|
||||
select_condition: "Please select a condition",
|
||||
contains: "Contains",
|
||||
not_contains: "Not contains",
|
||||
equals: "Equals",
|
||||
start_with: "Start with",
|
||||
end_with: "End With",
|
||||
value: "Value",
|
||||
expression: "Expression",
|
||||
response_in_time: "Response in time",
|
||||
},
|
||||
extract: "Extract from response",
|
||||
}
|
||||
},
|
||||
api_report: {
|
||||
request: "Request",
|
||||
request_body: "Body",
|
||||
request_headers: "Headers",
|
||||
request_cookie: "Cookie",
|
||||
response: "Response",
|
||||
delete_confirm: 'Confirm delete : ',
|
||||
scenario_name: "Scenario name",
|
||||
response_time: "Response time(ms)",
|
||||
latency: "Latency",
|
||||
request_size: "Request Size",
|
||||
response_size: "Response Size",
|
||||
response_code: "Response Code",
|
||||
response_message: "Response Message",
|
||||
error: "Error",
|
||||
assertions: "Assertions",
|
||||
assertions_pass: "Passed Assertions",
|
||||
assertions_name: "Assertion Name",
|
||||
assertions_message: "Assertion Message",
|
||||
assertions_is_success: "Fail/Success",
|
||||
result: "Result",
|
||||
success: "Success",
|
||||
fail: "Fail",
|
||||
},
|
||||
test_track: {
|
||||
test_track: "Test Track",
|
||||
|
|
|
@ -230,7 +230,7 @@ export default {
|
|||
response_time: "响应时间",
|
||||
select_type: "请选择类型",
|
||||
select_subject: "请选择对象",
|
||||
select_contains: "请选择条件",
|
||||
select_condition: "请选择条件",
|
||||
contains: "包含",
|
||||
not_contains: "不包含",
|
||||
equals: "等于",
|
||||
|
@ -243,6 +243,30 @@ export default {
|
|||
extract: "提取",
|
||||
}
|
||||
},
|
||||
api_report: {
|
||||
request: "请求",
|
||||
request_body: "请求内容",
|
||||
request_headers: "请求头",
|
||||
request_cookie: "Cookie",
|
||||
response: "响应",
|
||||
delete_confirm: '确认删除报告: ',
|
||||
scenario_name: "场景名称",
|
||||
response_time: "响应时间(ms)",
|
||||
latency: "网络延迟",
|
||||
request_size: "请求大小",
|
||||
response_size: "响应大小",
|
||||
response_code: "状态码",
|
||||
response_message: "响应报文",
|
||||
error: "错误",
|
||||
assertions: "断言",
|
||||
assertions_pass: "成功断言",
|
||||
assertions_name: "断言名称",
|
||||
assertions_message: "断言信息",
|
||||
assertions_is_success: "是否成功",
|
||||
result: "结果",
|
||||
success: "成功",
|
||||
fail: "失败",
|
||||
},
|
||||
test_track: {
|
||||
test_track: "测试跟踪",
|
||||
confirm: "确 定",
|
||||
|
|
Loading…
Reference in New Issue