Conflicts:
	frontend/src/business/components/common/components/MsScheduleEdit.vue
This commit is contained in:
wenyann 2020-09-02 16:24:27 +08:00
commit af53cf7c47
49 changed files with 669 additions and 512 deletions

View File

@ -20,7 +20,7 @@ MeterSphere 是一站式的开源企业级持续测试平台,涵盖测试跟
## UI 展示
![UI](https://metersphere.io/images/screenshot/ss07.png)
![UI](https://metersphere.io/images/screenshot/ss01.png)
## 在线体验
- 环境地址https://demo.metersphere.com/
@ -49,6 +49,9 @@ curl -sSL https://github.com/metersphere/metersphere/releases/latest/download/qu
- [完整文档](https://metersphere.io/docs/)
- [演示视频](http://video.fit2cloud.com/%E3%80%90%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%E3%80%91202006%20MeterSphere%20v1.0%20%E5%8A%9F%E8%83%BD%E6%BC%94%E7%A4%BA.mp4)
## MeterSphere 企业版
[申请企业版使用](https://jinshuju.net/f/CzzAOe)
## 相关工具
- [Jenkins 插件](https://github.com/metersphere/jenkins-plugin)

View File

@ -66,7 +66,7 @@ public class APITestController {
@PostMapping(value = "/update", consumes = {"multipart/form-data"})
public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
apiTestService.update(request, file, bodyFiles);
apiTestService.update(request, file, bodyFiles);
}
@PostMapping(value = "/copy")

View File

@ -47,4 +47,6 @@ public class HttpRequest implements Request {
private Long connectTimeout;
@JSONField(ordinal = 15)
private Long responseTimeout;
@JSONField(ordinal = 16)
private Boolean followRedirects;;
}

View File

@ -157,7 +157,7 @@ public class JmeterDocumentParser {
u += k + "=" + ScriptEngineUtils.calculate(v);
return u;
});
ele.setTextContent(url + params);
ele.setTextContent(url + ((params != null && !params.equals("?")) ? params : ""));
break;
case "Argument.value":
String textContent = ele.getTextContent();

View File

@ -1,8 +1,11 @@
package io.metersphere.excel.utils;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.exception.ExcelException;
import org.apache.poi.ss.usermodel.IndexedColors;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@ -29,9 +32,12 @@ public class EasyExcelExporter {
public void export(HttpServletResponse response, List data, String fileName, String sheetName) {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
contentWriteCellStyle.setWrapped(true);
try {
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(null, contentWriteCellStyle);
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");
EasyExcel.write(response.getOutputStream(), this.clazz).sheet(sheetName).doWrite(data);
EasyExcel.write(response.getOutputStream(), this.clazz).registerWriteHandler(horizontalCellStyleStrategy).sheet(sheetName).doWrite(data);
} catch (UnsupportedEncodingException e) {
LogUtil.error(e.getMessage(), e);
throw new ExcelException("Utf-8 encoding is not supported");

View File

@ -11,6 +11,7 @@ import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.dto.LogDetailDTO;
import io.metersphere.dto.ReportDTO;
import io.metersphere.performance.base.*;
import io.metersphere.performance.controller.request.DeleteReportRequest;
import io.metersphere.performance.controller.request.ReportRequest;
import io.metersphere.performance.service.ReportService;
import org.apache.shiro.authz.annotation.Logical;
@ -113,4 +114,9 @@ public class PerformanceReportController {
public void downloadLog(@PathVariable String reportId, @PathVariable String resourceId, HttpServletResponse response) throws Exception {
reportService.downloadLog(response, reportId, resourceId);
}
@PostMapping("/batch/delete")
public void deleteReportBatch(@RequestBody DeleteReportRequest reportRequest) {
reportService.deleteReportBatch(reportRequest);
}
}

View File

@ -0,0 +1,13 @@
package io.metersphere.performance.controller.request;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class DeleteReportRequest {
private List<String> ids;
}

View File

@ -34,6 +34,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
@ -42,8 +43,6 @@ import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Resource;
@Service
@Transactional(rollbackFor = Exception.class)
public class PerformanceTestService {
@ -381,6 +380,10 @@ public class PerformanceTestService {
if (!CollectionUtils.isEmpty(loadTestFiles)) {
loadTestFiles.forEach(loadTestFile -> {
FileMetadata fileMetadata = fileService.copyFile(loadTestFile.getFileId());
if (fileMetadata == null) {
// 如果性能测试出现文件变更这里会有 null
return;
}
loadTestFile.setTestId(copy.getId());
loadTestFile.setFileId(fileMetadata.getId());
loadTestFileMapper.insert(loadTestFile);

View File

@ -14,6 +14,7 @@ import io.metersphere.controller.request.OrderRequest;
import io.metersphere.dto.LogDetailDTO;
import io.metersphere.dto.ReportDTO;
import io.metersphere.performance.base.*;
import io.metersphere.performance.controller.request.DeleteReportRequest;
import io.metersphere.performance.controller.request.ReportRequest;
import io.metersphere.performance.engine.Engine;
import io.metersphere.performance.engine.EngineFactory;
@ -72,18 +73,22 @@ public class ReportService {
LogUtil.info("Delete report started, report ID: %s" + reportId);
final Engine engine = EngineFactory.createEngine(loadTest);
if (engine == null) {
MSException.throwException(String.format("Delete report fail. create engine failreport ID%s", reportId));
}
try {
final Engine engine = EngineFactory.createEngine(loadTest);
if (engine == null) {
MSException.throwException(String.format("Delete report fail. create engine failreport ID%s", reportId));
}
String reportStatus = loadTestReport.getStatus();
boolean isRunning = StringUtils.equals(reportStatus, PerformanceTestStatus.Running.name());
boolean isStarting = StringUtils.equals(reportStatus, PerformanceTestStatus.Starting.name());
boolean isError = StringUtils.equals(reportStatus, PerformanceTestStatus.Error.name());
if (isRunning || isStarting || isError) {
LogUtil.info("Start stop engine, report status: %s" + reportStatus);
stopEngine(loadTest, engine);
String reportStatus = loadTestReport.getStatus();
boolean isRunning = StringUtils.equals(reportStatus, PerformanceTestStatus.Running.name());
boolean isStarting = StringUtils.equals(reportStatus, PerformanceTestStatus.Starting.name());
boolean isError = StringUtils.equals(reportStatus, PerformanceTestStatus.Error.name());
if (isRunning || isStarting || isError) {
LogUtil.info("Start stop engine, report status: %s" + reportStatus);
stopEngine(loadTest, engine);
}
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
}
// delete load_test_report_result
@ -246,4 +251,9 @@ public class ReportService {
report.setStatus(status);
loadTestReportMapper.updateByPrimaryKeySelective(report);
}
public void deleteReportBatch(DeleteReportRequest reportRequest) {
List<String> ids = reportRequest.getIds();
ids.forEach(this::deleteReport);
}
}

View File

@ -60,6 +60,10 @@ public class FileService {
FileContentExample example2 = new FileContentExample();
example2.createCriteria().andFileIdIn(ids);
fileContentMapper.deleteByExample(example2);
LoadTestFileExample example3 = new LoadTestFileExample();
example3.createCriteria().andFileIdIn(ids);
loadTestFileMapper.deleteByExample(example3);
}
public FileMetadata saveFile(MultipartFile file) {

View File

@ -224,6 +224,10 @@ public class IssuesService {
String account = object.getString("account");
String password = object.getString("password");
String url = object.getString("url");
String issuetype = object.getString("issuetype");
if (StringUtils.isBlank(issuetype)) {
MSException.throwException("Jira 问题类型为空");
}
String auth = EncryptUtils.base64Encoding(account + ":" + password);
String testCaseId = issuesRequest.getTestCaseId();
@ -252,8 +256,7 @@ public class IssuesService {
" \"summary\":\"" + issuesRequest.getTitle() + "\",\n" +
" \"description\": " + JSON.toJSONString(desc) + ",\n" +
" \"issuetype\":{\n" +
" \"id\":\"10009\",\n" +
" \"name\":\"Defect\"\n" +
" \"name\":\"" + issuetype + "\"\n" +
" }\n" +
" }\n" +
"}";

View File

@ -1,4 +1,3 @@
export HEAP=$(cat /proc/meminfo | grep MemTotal | awk '{ mem=int($2/1024/1024 * 3/4 + 0.5); metasize=int(mem/4+0.5)"g"; if(mem<1) mem=1; if (metasize == "0g") metasize="256m"; HEAP="-Xms"mem"g -Xmx"mem"g -XX:MaxMetaspaceSize="metasize; print HEAP }')
for file in ${TESTS_DIR}/*.jmx; do
jmeter -n -t ${file} -Jserver.rmi.ssl.disable=${SSL_DISABLED}
done

View File

@ -171,7 +171,7 @@ export default {
<style scoped>
.report-container {
height: calc(100vh - 150px);
height: calc(100vh - 155px);
min-height: 600px;
overflow-y: auto;
}

View File

@ -344,7 +344,7 @@ export default {
<style scoped>
.test-container {
height: calc(100vh - 150px);
height: calc(100vh - 155px);
min-height: 600px;
}

View File

@ -17,7 +17,7 @@
<el-table border :data="tableData" class="adjust-table table-content" @sort-change="sort"
@row-click="handleView"
@filter-change="filter" @select-all="handleSelectAll" @select="selectionChange">
@filter-change="filter" @select-all="select" @select="select">
<el-table-column
type="selection"></el-table-column>
<el-table-column prop="name" :label="$t('commons.name')" width="250" show-overflow-tooltip>
@ -127,18 +127,7 @@
create() {
this.$router.push('/api/test/create');
},
handleSelectAll(selection) {
this.selectIds.clear()
this.selectProjectNames.clear()
this.selectProjectId.clear()
selection.forEach(s => {
this.selectIds.add(s.id)
this.selectProjectNames.add(s.projectName)
this.selectProjectId.add(s.projectId)
})
},
selectionChange(selection) {
select(selection) {
this.selectIds.clear()
this.selectProjectNames.clear()
this.selectProjectId.clear()

View File

@ -26,7 +26,7 @@
import MsApiScenarioConfig from "./components/ApiScenarioConfig";
import MsApiReportStatus from "../report/ApiReportStatus";
import MsApiReportDialog from "./ApiReportDialog";
import {getUUID} from "../../../../common/js/utils";
import {getUUID} from "@/common/js/utils";
export default {
@ -95,13 +95,12 @@
},
checkName(callback) {
for (let i of this.selectProjectId) {
this.result = this.$post('/api/checkName', {name: this.ruleForm.testName, projectId: i}, response => {
this.result = this.$post('/api/checkName', {name: this.ruleForm.testName, projectId: i}, () => {
if (callback) callback();
})
}
},
_getEnvironmentAndRunTest: function (item) {
let count = 0;
this.result = this.$get('/api/environment/list/' + item.projectId, response => {
let environments = response.data;
let environmentMap = new Map();
@ -131,7 +130,6 @@
scenarioDefinition: JSON.parse(item.scenarioDefinition),
schedule: {},
});
console.log(test)
this.test = this.test || test;
if (this.tests.length > 1) {
this.test.scenarioDefinition = this.test.scenarioDefinition.concat(test.scenarioDefinition);

View File

@ -1,5 +1,5 @@
<template>
<el-dialog :title="$t('api_test.environment.environment_config')" :visible.sync="visible" class="environment-dialog"
<el-dialog :close-on-click-modal="false" :title="$t('api_test.environment.environment_config')" :visible.sync="visible" class="environment-dialog"
@close="close" append-to-body ref="environmentConfig">
<el-container v-loading="result.loading">
<ms-aside-item :enable-aside-hidden="false" :title="$t('api_test.environment.environment_list')" :data="environments" :item-operators="environmentOperators" :add-fuc="addEnvironment"

View File

@ -7,10 +7,11 @@
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
<el-col>
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200" @change="change" :placeholder="keyText" show-word-limit>
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200"
@change="change" :placeholder="keyText" show-word-limit>
<template v-slot:prepend>
<el-select v-if="type === 'body'" :disabled="isReadOnly" class="kv-type" v-model="item.type">
<el-option value="text" />
<el-select v-if="type === 'body'" :disabled="isReadOnly" class="kv-type" v-model="item.type">
<el-option value="text"/>
<el-option value="file"/>
</el-select>
</template>
@ -31,12 +32,12 @@
value-key="name"
highlight-first-item
@select="change">
<i slot="suffix" class="el-input__icon el-icon-edit" style="cursor: pointer;" @click="advanced(item)"></i>
<i slot="suffix" class="el-input__icon el-icon-edit pointer" @click="advanced(item)"></i>
</el-autocomplete>
</el-col>
<el-col v-if="item.type === 'file'">
<ms-api-body-file-upload :parameter="item"/>
<ms-api-body-file-upload :parameter="item"/>
</el-col>
<el-col class="kv-delete">
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
@ -155,31 +156,36 @@ export default {
</script>
<style scoped>
.kv-description {
font-size: 13px;
}
.kv-description {
font-size: 13px;
}
.kv-row {
margin-top: 10px;
}
.kv-row {
margin-top: 10px;
}
.kv-delete {
width: 60px;
}
.kv-delete {
width: 60px;
}
.el-autocomplete {
width: 100%;
}
.el-autocomplete {
width: 100%;
}
.advanced-item-value >>> .el-dialog__body {
padding: 15px 25px;
}
.advanced-item-value >>> .el-dialog__body {
padding: 15px 25px;
}
.el-row {
margin-bottom: 5px;
}
.el-row {
margin-bottom: 5px;
}
.kv-type {
width: 70px;
}
.kv-type {
width: 70px;
}
.pointer {
cursor: pointer;
color: #1E90FF;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<el-dialog :title="$t('api_test.api_import.title')" :visible.sync="visible" class="api-import" v-loading="result.loading" @close="close">
<el-dialog :close-on-click-modal="false" :title="$t('api_test.api_import.title')" :visible.sync="visible" class="api-import" v-loading="result.loading" @close="close">
<div class="header-bar">
<div>{{$t('api_test.api_import.data_format')}}</div>

View File

@ -38,6 +38,7 @@
v-model="request.useEnvironment"
:active-text="$t('api_test.request.refer_to_environment')" @change="useEnvironmentChange">
</el-switch>
<el-checkbox class="follow-redirects-item" v-model="request.followRedirects">{{$t('api_test.request.follow_redirects')}}</el-checkbox>
</el-form-item>
<el-button :disabled="!request.enable || !scenario.enable || isReadOnly" class="debug-button" size="small" type="primary" @click="runDebug">{{ $t('api_test.request.debug') }}</el-button>
@ -221,4 +222,8 @@ export default {
color: #F56C6C;
}
.follow-redirects-item {
margin-left: 30px;
}
</style>

View File

@ -295,8 +295,10 @@ export class HTTPSamplerProxy extends DefaultTestElement {
if (options.responseTimeout) {
this.stringProp('HTTPSampler.response_timeout', options.responseTimeout);
}
if (options.followRedirects) {
this.boolProp('HTTPSampler.follow_redirects', options.followRedirects, true);
}
this.boolProp("HTTPSampler.follow_redirects", options.follow, true);
this.boolProp("HTTPSampler.use_keepalive", options.keepalive, true);
}
}

View File

@ -312,6 +312,7 @@ export class HttpRequest extends Request {
this.enable = true;
this.connectTimeout = 60*1000;
this.responseTimeout = undefined;
this.followRedirects = true;
this.set(options);
this.sets({parameters: KeyValue, headers: KeyValue}, options);
@ -749,6 +750,7 @@ class JMXHttpRequest {
}
this.connectTimeout = request.connectTimeout;
this.responseTimeout = request.responseTimeout;
this.followRedirects = request.followRedirects;
}
}
@ -867,11 +869,11 @@ class JMXGenerator {
}
}
this.addBeanShellProcessor(sampler, request);
this.addRequestExtractor(sampler, request);
this.addRequestAssertion(sampler, request);
this.addRequestExtractor(sampler, request);
this.addBeanShellProcessor(sampler, request);
threadGroup.put(sampler);
}

View File

@ -1,5 +1,6 @@
<template>
<el-dialog :title="title"
<el-dialog :close-on-click-modal="false"
:title="title"
:visible.sync="dialogVisible"
class="delete-confirm" >

View File

@ -1,104 +1,110 @@
<template>
<div class="schedule-config">
<div>
<div class="schedule-config">
<div>
<span class="cron-ico" @click="scheduleEdit">
<i class="el-icon-date" size="small"></i>
<span class="character">SCHEDULER</span>
</span>
<el-switch :disabled="!schedule.value || isReadOnly" v-model="schedule.enable" @change="scheduleChange"/>
<ms-schedule-edit :is-read-only="isReadOnly" :schedule="schedule" :save="save" :custom-validate="customValidate" ref="scheduleEdit"/>
<el-switch :disabled="!schedule.value || isReadOnly" v-model="schedule.enable" @change="scheduleChange"/>
<ms-schedule-edit :is-read-only="isReadOnly" :schedule="schedule" :save="save" :custom-validate="customValidate"
ref="scheduleEdit"/>
</div>
<div>
</div>
<div>
<span>
{{$t('schedule.next_execution_time')}}
<span :class="{'disable-character': !schedule.enable}" v-if="!schedule.enable">{{$t('schedule.not_set')}}</span>
{{ $t('schedule.next_execution_time') }}
<span :class="{'disable-character': !schedule.enable}"
v-if="!schedule.enable">{{ $t('schedule.not_set') }}</span>
<crontab-result v-if="schedule.enable" :enable-simple-mode="true" :ex="schedule.value" ref="crontabResult"/>
</span>
</div>
</div>
</div>
</template>
<script>
import MsScheduleEdit from "./MsScheduleEdit";
import CrontabResult from "../cron/CrontabResult";
import MsScheduleEdit from "./MsScheduleEdit";
import CrontabResult from "../cron/CrontabResult";
function defaultCustomValidate() {return {pass: true};}
function defaultCustomValidate() {
return {pass: true};
}
export default {
name: "MsScheduleConfig",
components: {CrontabResult, MsScheduleEdit},
data() {
export default {
name: "MsScheduleConfig",
components: {CrontabResult, MsScheduleEdit},
data() {
return {
recentList: [],
}
},
props: {
save: Function,
schedule: {},
checkOpen: {
type: Function,
default() {
return {
recentList: [],
}
},
props: {
save: Function,
schedule: {},
checkOpen: {
type: Function,
default() {
return {
checkOpen() {return true;}
}
checkOpen() {
return true;
}
},
customValidate: {
type: Function,
default: defaultCustomValidate
},
isReadOnly: {
type: Boolean,
default: false
}
},
methods: {
scheduleEdit() {
if (!this.checkOpen()) {
return;
}
this.$refs.scheduleEdit.open();
},
scheduleChange() {
this.$emit('scheduleChange');
},
flashResultList() {
this.$refs.crontabResult.expressionChange();
}
}
},
customValidate: {
type: Function,
default: defaultCustomValidate
},
isReadOnly: {
type: Boolean,
default: false
}
},
methods: {
scheduleEdit() {
if (!this.checkOpen()) {
return;
}
this.$refs.scheduleEdit.open();
},
scheduleChange() {
this.$emit('scheduleChange');
},
flashResultList() {
this.$refs.crontabResult.expressionChange();
}
}
}
</script>
<style scoped>
.schedule-config {
float: right;
width: 250px;
height: 15px;
line-height: 25px;
}
.schedule-config {
float: right;
width: 250px;
height: 15px;
line-height: 25px;
}
.el-icon-date {
font-size: 20px;
margin-left: 5px;
}
.el-icon-date {
font-size: 20px;
margin-left: 5px;
}
.character {
font-weight: bold;
margin: 0 5px;
}
.character {
font-weight: bold;
margin: 0 5px;
}
.disable-character {
color: #cccccc;
}
.disable-character {
color: #cccccc;
}
.el-switch {
margin: 0 5px;
}
.el-switch {
margin: 0 5px;
}
.cron-ico {
cursor: pointer;
}
.cron-ico {
cursor: pointer;
}
</style>

View File

@ -1,5 +1,6 @@
<template>
<el-dialog :close-on-click-modal="false" width="50%" class="schedule-edit" :visible.sync="dialogVisible"
<el-dialog :close-on-click-modal="false" width="70%" class="schedule-edit" :visible.sync="dialogVisible"
@close="close">
<template>
<div>
@ -111,14 +112,14 @@
<script>
import Crontab from "../cron/Crontab";
import CrontabResult from "../cron/CrontabResult";
import {cronValidate} from "../../../../common/js/cron";
import {listenGoBack, removeGoBackListener} from "../../../../common/js/utils";
import Crontab from "../cron/Crontab";
import CrontabResult from "../cron/CrontabResult";
import {cronValidate} from "@/common/js/cron";
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
function defaultCustomValidate() {
return {pass: true};
}
function defaultCustomValidate() {
return {pass: true};
}
export default {
name: "MsScheduleEdit",
@ -271,13 +272,13 @@
<style scoped>
.inp {
width: 50%;
margin-right: 20px;
}
.inp {
width: 50%;
margin-right: 20px;
}
.el-form-item {
margin-bottom: 10px;
}
.el-form-item {
margin-bottom: 10px;
}
</style>

View File

@ -22,9 +22,9 @@
@click="rerun(testId)">
{{ $t('report.test_execute_again') }}
</el-button>
<!-- <el-button :disabled="isReadOnly" type="info" plain size="mini" @click="exports(reportName)">
{{$t('report.export')}}
</el-button>-->
<!-- <el-button :disabled="isReadOnly" type="info" plain size="mini" @click="exports(reportName)">
{{$t('report.export')}}
</el-button>-->
<!--
<el-button :disabled="isReadOnly" type="warning" plain size="mini">
{{$t('report.compare')}}
@ -82,252 +82,237 @@
</template>
<script>
import MsReportErrorLog from './components/ErrorLog';
import MsReportLogDetails from './components/LogDetails';
import MsReportRequestStatistics from './components/RequestStatistics';
import MsReportTestOverview from './components/TestOverview';
import MsPerformancePressureConfig from "./components/PerformancePressureConfig";
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import MsReportErrorLog from './components/ErrorLog';
import MsReportLogDetails from './components/LogDetails';
import MsReportRequestStatistics from './components/RequestStatistics';
import MsReportTestOverview from './components/TestOverview';
import MsPerformancePressureConfig from "./components/PerformancePressureConfig";
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import {checkoutTestManagerOrTestUser} from "@/common/js/utils";
import writer from "file-writer";
import ResumeCss from "../../../../common/css/main.css";
import {checkoutTestManagerOrTestUser} from "@/common/js/utils";
export default {
name: "PerformanceReportView",
components: {
MsReportErrorLog,
MsReportLogDetails,
MsReportRequestStatistics,
MsReportTestOverview,
MsContainer,
MsMainContainer,
MsPerformancePressureConfig
},
data() {
return {
result: {},
active: '1',
reportId: '',
status: '',
reportName: '',
testId: '',
testName: '',
projectId: '',
projectName: '',
startTime: '0',
endTime: '0',
minutes: '0',
seconds: '0',
title: 'Logging',
report: {},
isReadOnly: false,
websocket: null,
dialogFormVisible: false,
testPlan: {testResourcePoolId: null}
}
},
methods: {
initBreadcrumb() {
if (this.reportId) {
this.result = this.$get("/performance/report/test/pro/info/" + this.reportId, res => {
let data = res.data;
if (data) {
this.reportName = data.name;
this.testId = data.testId;
this.testName = data.testName;
this.projectId = data.projectId;
this.projectName = data.projectName;
}
})
}
},
initReportTimeInfo() {
if (this.reportId) {
this.result = this.$get("/performance/report/content/report_time/" + this.reportId)
.then(res => {
let data = res.data.data;
if (data) {
this.startTime = data.startTime;
this.endTime = data.endTime;
let duration = data.duration;
this.minutes = Math.floor(duration / 60);
this.seconds = duration % 60;
}
}).catch(() => {
this.clearData();
})
}
},
initWebSocket() {
let protocol = "ws://";
if (window.location.protocol === 'https:') {
protocol = "wss://";
}
const uri = protocol + window.location.host + "/performance/report/" + this.reportId;
this.websocket = new WebSocket(uri);
this.websocket.onmessage = this.onMessage;
this.websocket.onopen = this.onOpen;
this.websocket.onerror = this.onError;
this.websocket.onclose = this.onClose;
},
checkReportStatus(status) {
switch (status) {
case 'Error':
this.$warning(this.$t('report.generation_error'));
break;
case 'Starting':
this.$alert(this.$t('report.start_status'));
break;
case 'Reporting':
case 'Running':
case 'Completed':
default:
break;
}
},
clearData() {
this.startTime = '0';
this.endTime = '0';
this.minutes = '0';
this.seconds = '0';
},
stopTest(forceStop) {
this.result = this.$get('/performance/stop/' + this.reportId + '/' + forceStop, () => {
this.$success(this.$t('report.test_stop_success'));
if (forceStop) {
this.$router.push('/performance/report/all');
export default {
name: "PerformanceReportView",
components: {
MsReportErrorLog,
MsReportLogDetails,
MsReportRequestStatistics,
MsReportTestOverview,
MsContainer,
MsMainContainer,
MsPerformancePressureConfig
},
data() {
return {
result: {},
active: '1',
reportId: '',
status: '',
reportName: '',
testId: '',
testName: '',
projectId: '',
projectName: '',
startTime: '0',
endTime: '0',
minutes: '0',
seconds: '0',
title: 'Logging',
report: {},
isReadOnly: false,
websocket: null,
dialogFormVisible: false,
testPlan: {testResourcePoolId: null}
}
},
methods: {
initBreadcrumb(callback) {
if (this.reportId) {
this.result = this.$get("/performance/report/test/pro/info/" + this.reportId, res => {
let data = res.data;
if (data) {
this.reportName = data.name;
this.testId = data.testId;
this.testName = data.testName;
this.projectId = data.projectId;
this.projectName = data.projectName;
//
if (callback) callback(res);
} else {
this.report.status = 'Completed';
this.$error(this.$t('report.not_exist'));
}
});
this.dialogFormVisible = false;
},
rerun(testId) {
this.$confirm(this.$t('report.test_rerun_confirm'), '', {
confirmButtonText: this.$t('commons.confirm'),
cancelButtonText: this.$t('commons.cancel'),
type: 'warning'
}).then(() => {
this.result = this.$post('/performance/run', {id: testId, triggerMode: 'MANUAL'}, (response) => {
this.reportId = response.data;
this.$router.push({path: '/performance/report/view/' + this.reportId});
// socket
this.initWebSocket();
})
}).catch(() => {
});
},
onOpen() {
window.console.log("socket opening.");
},
onError(e) {
window.console.error(e)
},
onMessage(e) {
this.$set(this.report, "refresh", e.data); //
this.$set(this.report, "status", 'Running');
this.initReportTimeInfo();
window.console.log('receive a message:', e.data);
},
onClose(e) {
this.$set(this.report, "refresh", Math.random()); //
this.$set(this.report, "status", 'Completed');
this.initReportTimeInfo();
window.console.log("socket closed.");
})
}
},
created() {
this.isReadOnly = false;
if (!checkoutTestManagerOrTestUser()) {
this.isReadOnly = true;
initReportTimeInfo() {
if (this.status === 'Starting') {
this.clearData();
return;
}
this.reportId = this.$route.path.split('/')[4];
this.result = this.$get("/performance/report/" + this.reportId, res => {
let data = res.data;
if (data) {
this.status = data.status;
this.$set(this.report, "id", this.reportId);
this.$set(this.report, "status", data.status);
this.$set(this.report, "testId", data.testId);
this.$set(this.report, "loadConfiguration", data.loadConfiguration);
this.checkReportStatus(data.status);
if (this.status === "Completed" || this.status === "Running") {
this.initReportTimeInfo();
}
this.initBreadcrumb();
this.initWebSocket();
if (this.reportId) {
this.result = this.$get("/performance/report/content/report_time/" + this.reportId)
.then(res => {
let data = res.data.data;
if (data) {
this.startTime = data.startTime;
this.endTime = data.endTime;
let duration = data.duration;
this.minutes = Math.floor(duration / 60);
this.seconds = duration % 60;
}
}).catch(() => {
this.clearData();
});
}
},
initWebSocket() {
let protocol = "ws://";
if (window.location.protocol === 'https:') {
protocol = "wss://";
}
const uri = protocol + window.location.host + "/performance/report/" + this.reportId;
this.websocket = new WebSocket(uri);
this.websocket.onmessage = this.onMessage;
this.websocket.onopen = this.onOpen;
this.websocket.onerror = this.onError;
this.websocket.onclose = this.onClose;
},
checkReportStatus(status) {
switch (status) {
case 'Error':
this.$warning(this.$t('report.generation_error'));
break;
case 'Starting':
this.$alert(this.$t('report.start_status'));
break;
case 'Reporting':
case 'Running':
case 'Completed':
default:
break;
}
},
clearData() {
this.startTime = '0';
this.endTime = '0';
this.minutes = '0';
this.seconds = '0';
},
stopTest(forceStop) {
this.result = this.$get('/performance/stop/' + this.reportId + '/' + forceStop, () => {
this.$success(this.$t('report.test_stop_success'));
if (forceStop) {
this.$router.push('/performance/report/all');
} else {
this.$error(this.$t('report.not_exist'))
this.report.status = 'Completed';
}
});
this.dialogFormVisible = false;
},
beforeDestroy() {
this.websocket.close() //websocket
rerun(testId) {
this.$confirm(this.$t('report.test_rerun_confirm'), '', {
confirmButtonText: this.$t('commons.confirm'),
cancelButtonText: this.$t('commons.cancel'),
type: 'warning'
}).then(() => {
this.result = this.$post('/performance/run', {id: testId, triggerMode: 'MANUAL'}, (response) => {
this.reportId = response.data;
this.$router.push({path: '/performance/report/view/' + this.reportId});
// socket
this.initWebSocket();
})
}).catch(() => {
});
},
watch: {
'$route'(to) {
if (to.name === "perReportView") {
this.isReadOnly = false;
if (!checkoutTestManagerOrTestUser()) {
this.isReadOnly = true;
}
let reportId = to.path.split('/')[4];
this.reportId = reportId;
if (reportId) {
this.$get("/performance/report/test/pro/info/" + reportId, response => {
let data = response.data;
if (data) {
this.status = data.status;
this.reportName = data.name;
this.testName = data.testName;
this.testId = data.testId;
this.projectName = data.projectName;
this.$set(this.report, "id", reportId);
this.$set(this.report, "status", data.status);
this.checkReportStatus(data.status);
if (this.status === "Completed") {
this.result = this.$get("/performance/report/content/report_time/" + this.reportId).then(res => {
let data = res.data.data;
if (data) {
this.startTime = data.startTime;
this.endTime = data.endTime;
let duration = data.duration;
this.minutes = Math.floor(duration / 60);
this.seconds = duration % 60;
}
}).catch(() => {
this.clearData();
})
} else {
this.clearData();
}
} else {
this.$error(this.$t('report.not_exist'));
}
});
}
onOpen() {
window.console.log("socket opening.");
},
onError(e) {
window.console.error(e)
},
onMessage(e) {
this.$set(this.report, "refresh", e.data); //
this.$set(this.report, "status", 'Running');
this.status = 'Running';
this.initReportTimeInfo();
window.console.log('receive a message:', e.data);
},
onClose(e) {
if (e.code === 1005) {
// socketreport
return;
}
this.$set(this.report, "refresh", Math.random()); //
this.$set(this.report, "status", 'Completed');
this.initReportTimeInfo();
window.console.log("socket closed.");
}
},
created() {
this.isReadOnly = false;
if (!checkoutTestManagerOrTestUser()) {
this.isReadOnly = true;
}
this.reportId = this.$route.path.split('/')[4];
this.result = this.$get("/performance/report/" + this.reportId, res => {
let data = res.data;
if (data) {
this.status = data.status;
this.$set(this.report, "id", this.reportId);
this.$set(this.report, "status", data.status);
this.$set(this.report, "testId", data.testId);
this.$set(this.report, "loadConfiguration", data.loadConfiguration);
this.checkReportStatus(data.status);
if (this.status === "Completed" || this.status === "Running") {
this.initReportTimeInfo();
}
this.initBreadcrumb();
this.initWebSocket();
} else {
this.$error(this.$t('report.not_exist'))
}
});
},
watch: {
'$route'(to) {
if (to.name === "perReportView") {
this.isReadOnly = false;
if (!checkoutTestManagerOrTestUser()) {
this.isReadOnly = true;
}
let reportId = to.path.split('/')[4];
this.reportId = reportId;
this.initBreadcrumb((response) => {
let data = response.data;
this.$set(this.report, "id", reportId);
this.$set(this.report, "status", data.status);
this.checkReportStatus(data.status);
this.initReportTimeInfo();
});
this.initWebSocket();
} else {
console.log("close socket.");
this.websocket.close() //websocket
}
}
}
}
</script>
<style scoped>
.ms-report-view-btns {
margin-top: 15px;
}
.ms-report-view-btns {
margin-top: 15px;
}
.ms-report-time-desc {
text-align: left;
display: block;
color: #5C7878;
}
.ms-report-time-desc {
text-align: left;
display: block;
color: #5C7878;
}
</style>

View File

@ -9,9 +9,18 @@
</template>
<el-table border :data="tableData" class="adjust-table test-content"
@select-all="handleSelectAll"
@select="handleSelect"
@sort-change="sort"
@filter-change="filter"
>
<el-table-column
type="selection"/>
<el-table-column width="40" :resizable="false" align="center">
<template v-slot:default="scope">
<show-more-btn :is-show="scope.row.showMore" :buttons="buttons" :size="selectRows.size"/>
</template>
</el-table-column>
<el-table-column
prop="name"
:label="$t('commons.name')"
@ -89,13 +98,19 @@ import ReportTriggerModeItem from "../../common/tableItem/ReportTriggerModeItem"
import {REPORT_CONFIGS} from "../../common/components/search/search-components";
import MsTableHeader from "../../common/components/MsTableHeader";
import {LIST_CHANGE, PerformanceEvent} from "@/business/components/common/head/ListEvent";
import ShowMoreBtn from "../../track/case/components/ShowMoreBtn";
export default {
name: "PerformanceTestReport",
components: {
MsTableHeader,
ReportTriggerModeItem,
MsTableOperatorButton, MsPerformanceReportStatus, MsTablePagination, MsContainer, MsMainContainer
MsTableOperatorButton,
MsPerformanceReportStatus,
MsTablePagination,
MsContainer,
MsMainContainer,
ShowMoreBtn,
},
created: function () {
this.initTableData();
@ -128,6 +143,12 @@ export default {
{text: '定时任务', value: 'SCHEDULE'},
{text: 'API', value: 'API'}
],
buttons: [
{
name: this.$t('report.batch_delete'), handleClick: this.handleBatchDelete
}
],
selectRows: new Set(),
}
},
watch: {
@ -194,6 +215,60 @@ export default {
_filter(filters, this.condition);
this.initTableData();
},
handleSelect(selection, row) {
if (this.selectRows.has(row)) {
this.$set(row, "showMore", false);
this.selectRows.delete(row);
} else {
this.$set(row, "showMore", true);
this.selectRows.add(row);
}
let arr = Array.from(this.selectRows);
// 1
if (this.selectRows.size === 1) {
this.$set(arr[0], "showMore", false);
} else if (this.selectRows.size === 2) {
arr.forEach(row => {
this.$set(row, "showMore", true);
})
}
},
handleSelectAll(selection) {
if (selection.length > 0) {
if (selection.length === 1) {
this.selectRows.add(selection[0]);
} else {
this.tableData.forEach(item => {
this.$set(item, "showMore", true);
this.selectRows.add(item);
});
}
} else {
this.selectRows.clear();
this.tableData.forEach(row => {
this.$set(row, "showMore", false);
})
}
},
handleBatchDelete() {
this.$alert(this.$t('report.delete_batch_confirm') + "", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
let ids = Array.from(this.selectRows).map(row => row.id);
this.result = this.$post('/performance/report/batch/delete', {ids: ids}, () => {
this.selectRows.clear();
this.$success(this.$t('commons.delete_success'));
this.search();
// 广 head
PerformanceEvent.$emit(LIST_CHANGE);
});
}
}
});
}
}
}
</script>

View File

@ -58,7 +58,7 @@ import PerformancePressureConfig from "./components/PerformancePressureConfig";
import PerformanceAdvancedConfig from "./components/PerformanceAdvancedConfig";
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import {checkoutTestManagerOrTestUser} from "../../../../common/js/utils";
import {checkoutTestManagerOrTestUser} from "@/common/js/utils";
import MsScheduleConfig from "../../common/components/MsScheduleConfig";
import {LIST_CHANGE, PerformanceEvent} from "@/business/components/common/head/ListEvent";

View File

@ -4,7 +4,7 @@
accept=".jmx,.csv"
drag
action=""
:limit="2"
:limit="5"
multiple
:show-file-list="false"
:before-upload="beforeUpload"

View File

@ -164,6 +164,7 @@ export default {
} else {
this.calculateChart();
}
this.getResourcePools();
},
},
methods: {

View File

@ -49,7 +49,7 @@
</el-card>
</ms-main-container>
<el-dialog :title="title" :visible.sync="createVisible" destroy-on-close @close="handleClose">
<el-dialog :close-on-click-modal="false" :title="title" :visible.sync="createVisible" destroy-on-close @close="handleClose">
<el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"></el-input>

View File

@ -25,6 +25,9 @@
<el-form-item :label="$t('organization.integration.jira_url')" prop="url" v-if="platform === 'Jira'">
<el-input v-model="form.url" :placeholder="$t('organization.integration.input_jira_url')"/>
</el-form-item>
<el-form-item :label="$t('organization.integration.jira_issuetype')" prop="issuetype" v-if="platform === 'Jira'">
<el-input v-model="form.issuetype" :placeholder="$t('organization.integration.input_jira_issuetype')"/>
</el-form-item>
</el-form>
</div>
@ -43,10 +46,13 @@
<div class="defect-tip">
<div>{{$t('organization.integration.use_tip')}}</div>
<div>
1. {{$t('organization.integration.use_tip_one')}}
1. {{$t('organization.integration.use_tip_tapd')}}
</div>
<div>
2. {{$t('organization.integration.use_tip_two')}}
2. {{$t('organization.integration.use_tip_jira')}}
</div>
<div>
3. {{$t('organization.integration.use_tip_two')}}
<router-link to="/track/project/all" style="margin-left: 5px">
{{$t('organization.integration.link_the_project_now')}}
</router-link>
@ -85,6 +91,11 @@
required: true,
message: this.$t('organization.integration.input_jira_url'),
trigger: ['change', 'blur']
},
issuetype: {
required: true,
message: this.$t('organization.integration.input_jira_issuetype'),
trigger: ['change', 'blur']
}
},
}
@ -105,6 +116,7 @@
this.$set(this.form, 'account', config.account);
this.$set(this.form, 'password', config.password);
this.$set(this.form, 'url', config.url);
this.$set(this.form, 'issuetype', config.issuetype);
} else {
this.clear();
}
@ -149,17 +161,26 @@
this.$warning(this.$t('organization.integration.choose_platform'));
return;
}
let param = {};
let auth = {
account: this.form.account,
password: this.form.password,
url: this.form.url
};
param.organizationId = getCurrentUser().lastOrganizationId;
param.platform = this.platform;
param.configuration = JSON.stringify(auth);
this.$refs[form].validate(valid => {
if (valid) {
let formatUrl = this.form.url;
if (!formatUrl.endsWith('/')) {
formatUrl = formatUrl + '/';
}
let param = {};
let auth = {
account: this.form.account,
password: this.form.password,
url: formatUrl,
issuetype: this.form.issuetype
};
param.organizationId = getCurrentUser().lastOrganizationId;
param.platform = this.platform;
param.configuration = JSON.stringify(auth);
this.result = this.$post("service/integration/save", param, () => {
this.show = true;
this.showEdit = true;
@ -188,6 +209,7 @@
this.$set(this.form, 'account', config.account);
this.$set(this.form, 'password', config.password);
this.$set(this.form, 'url', config.url);
this.$set(this.form, 'issuetype', config.issuetype);
} else {
this.clear();
}
@ -197,6 +219,7 @@
this.$set(this.form, 'account', '');
this.$set(this.form, 'password', '');
this.$set(this.form, 'url', '');
this.$set(this.form, 'issuetype', '');
this.$nextTick(() => {
this.$refs.form.clearValidate();
});

View File

@ -25,7 +25,7 @@
:total="total"/>
</el-card>
<el-dialog :title="$t('member.create')" :visible.sync="createVisible" width="30%" :destroy-on-close="true"
<el-dialog :close-on-click-modal="false" :title="$t('member.create')" :visible.sync="createVisible" width="30%" :destroy-on-close="true"
@close="handleClose">
<el-form :model="form" ref="form" :rules="rules" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.member')" prop="ids"
@ -73,7 +73,7 @@
</template>
</el-dialog>
<el-dialog :title="$t('member.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true"
<el-dialog :close-on-click-modal="false" :title="$t('member.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true"
@close="handleClose">
<el-form :model="form" label-position="right" label-width="100px" size="small" ref="updateUserForm">
<el-form-item label="ID" prop="id">

View File

@ -24,7 +24,7 @@
:total="total"/>
</el-card>
<el-dialog :title="$t('workspace.create')" :visible.sync="dialogWsAddVisible" width="30%" @close="close">
<el-dialog :close-on-click-modal="false" :title="$t('workspace.create')" :visible.sync="dialogWsAddVisible" width="30%" @close="close">
<el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/>
@ -39,7 +39,7 @@
@confirm="submit('form')"/>
</template>
</el-dialog>
<el-dialog :title="$t('workspace.update')" :visible.sync="dialogWsUpdateVisible" width="30%">
<el-dialog :close-on-click-modal="false" :title="$t('workspace.update')" :visible.sync="dialogWsUpdateVisible" width="30%">
<el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/>
@ -56,7 +56,7 @@
</el-dialog>
<!-- dialog of workspace member -->
<el-dialog :visible.sync="dialogWsMemberVisible" width="70%" :destroy-on-close="true" @close="close"
<el-dialog :close-on-click-modal="false" :visible.sync="dialogWsMemberVisible" width="70%" :destroy-on-close="true" @close="close"
class="dialog-css">
<ms-table-header :condition.sync="dialogCondition" @create="addMember" @search="dialogSearch"
:create-tip="$t('member.create')" :title="$t('commons.member')"/>
@ -82,7 +82,7 @@
</el-dialog>
<!-- add workspace member dialog -->
<el-dialog :title="$t('member.create')" :visible.sync="dialogWsMemberAddVisible" width="30%"
<el-dialog :close-on-click-modal="false" :title="$t('member.create')" :visible.sync="dialogWsMemberAddVisible" width="30%"
:destroy-on-close="true"
@close="closeFunc">
<el-form :model="memberForm" ref="form" :rules="wsMemberRule" label-position="right" label-width="100px"
@ -125,7 +125,7 @@
</el-dialog>
<!-- update workspace member dialog -->
<el-dialog :title="$t('member.modify')" :visible.sync="dialogWsMemberUpdateVisible" width="30%"
<el-dialog :close-on-click-modal="false" :title="$t('member.modify')" :visible.sync="dialogWsMemberUpdateVisible" width="30%"
:destroy-on-close="true"
@close="closeFunc">
<el-form :model="memberForm" label-position="right" label-width="100px" size="small" ref="updateUserForm">

View File

@ -32,7 +32,7 @@
</el-card>
<!--Modify personal details-->
<el-dialog :title="$t('member.modify_personal_info')" :visible.sync="updateVisible" width="30%"
<el-dialog :close-on-click-modal="false" :title="$t('member.modify_personal_info')" :visible.sync="updateVisible" width="30%"
:destroy-on-close="true" @close="handleClose">
<el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule"
ref="updateUserForm">
@ -57,7 +57,7 @@
</el-dialog>
<!--Change personal password-->
<el-dialog :title="$t('member.edit_password')" :visible.sync="editPasswordVisible" width="35%"
<el-dialog :close-on-click-modal="false" :title="$t('member.edit_password')" :visible.sync="editPasswordVisible" width="35%"
:destroy-on-close="true" @close="handleClose" left>
<el-form :model="ruleForm" :rules="rules" ref="editPasswordForm" label-width="120px" class="demo-ruleForm">
<el-form-item :label="$t('member.old_password')" prop="password" style="margin-bottom: 29px">

View File

@ -28,7 +28,7 @@
</el-card>
<!-- dialog of organization member -->
<el-dialog :visible.sync="dialogOrgMemberVisible" width="70%" :destroy-on-close="true" @close="closeFunc"
<el-dialog :close-on-click-modal="false" :visible.sync="dialogOrgMemberVisible" width="70%" :destroy-on-close="true" @close="closeFunc"
class="dialog-css">
<ms-table-header :condition.sync="dialogCondition" @create="addMember" @search="dialogSearch"
:create-tip="$t('member.create')" :title="$t('commons.member')"/>
@ -56,7 +56,7 @@
</el-dialog>
<!-- add organization form -->
<el-dialog :title="$t('organization.create')" :visible.sync="dialogOrgAddVisible" width="30%" @closed="closeFunc"
<el-dialog :close-on-click-modal="false" :title="$t('organization.create')" :visible.sync="dialogOrgAddVisible" width="30%" @closed="closeFunc"
:destroy-on-close="true">
<el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule"
ref="createOrganization">
@ -75,7 +75,7 @@
</el-dialog>
<!-- update organization form -->
<el-dialog :title="$t('organization.modify')" :visible.sync="dialogOrgUpdateVisible" width="30%"
<el-dialog :close-on-click-modal="false" :title="$t('organization.modify')" :visible.sync="dialogOrgUpdateVisible" width="30%"
:destroy-on-close="true"
@close="closeFunc">
<el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule"
@ -95,7 +95,7 @@
</el-dialog>
<!-- add organization member form -->
<el-dialog :title="$t('member.create')" :visible.sync="dialogOrgMemberAddVisible" width="30%"
<el-dialog :close-on-click-modal="false" :title="$t('member.create')" :visible.sync="dialogOrgMemberAddVisible" width="30%"
:destroy-on-close="true"
@close="closeFunc">
<el-form :model="memberForm" ref="form" :rules="orgMemberRule" label-position="right" label-width="100px"
@ -133,7 +133,7 @@
</el-dialog>
<!-- update organization member form -->
<el-dialog :title="$t('member.modify')" :visible.sync="dialogOrgMemberUpdateVisible" width="30%"
<el-dialog :close-on-click-modal="false" :title="$t('member.modify')" :visible.sync="dialogOrgMemberUpdateVisible" width="30%"
:destroy-on-close="true"
@close="closeFunc">
<el-form :model="memberForm" label-position="right" label-width="100px" size="small" ref="updateUserForm">

View File

@ -28,7 +28,7 @@
</el-card>
<!-- add workspace dialog -->
<el-dialog :title="$t('workspace.create')" :visible.sync="dialogWsAddVisible" width="30%" @close="close">
<el-dialog :close-on-click-modal="false" :title="$t('workspace.create')" :visible.sync="dialogWsAddVisible" width="30%" @close="close">
<el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/>
@ -56,7 +56,7 @@
</el-dialog>
<!-- update workspace dialog -->
<el-dialog :title="$t('workspace.update')" :visible.sync="dialogWsUpdateVisible" width="30%" @close="close">
<el-dialog :close-on-click-modal="false" :title="$t('workspace.update')" :visible.sync="dialogWsUpdateVisible" width="30%" @close="close">
<el-form :model="form" :rules="rules" ref="updateForm" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/>
@ -85,7 +85,7 @@
</el-dialog>
<!-- dialog of workspace member -->
<el-dialog :visible.sync="dialogWsMemberVisible" width="70%" :destroy-on-close="true" @close="closeWsMemberDialog" class="dialog-css">
<el-dialog :close-on-click-modal="false" :visible.sync="dialogWsMemberVisible" width="70%" :destroy-on-close="true" @close="closeWsMemberDialog" class="dialog-css">
<ms-table-header :condition.sync="dialogCondition" @create="addMember" @search="dialogSearch"
:create-tip="$t('member.create')" :title="$t('commons.member')"/>
<!-- organization member table -->
@ -111,7 +111,7 @@
</el-dialog>
<!-- add workspace member dialog -->
<el-dialog :title="$t('member.create')" :visible.sync="dialogWsMemberAddVisible" width="30%"
<el-dialog :close-on-click-modal="false" :title="$t('member.create')" :visible.sync="dialogWsMemberAddVisible" width="30%"
:destroy-on-close="true"
@close="handleClose">
<el-form :model="memberForm" ref="form" :rules="wsMemberRule" label-position="right" label-width="100px"
@ -149,7 +149,7 @@
</el-dialog>
<!-- update workspace member dialog -->
<el-dialog :title="$t('member.modify')" :visible.sync="dialogWsMemberUpdateVisible" width="30%"
<el-dialog :close-on-click-modal="false" :title="$t('member.modify')" :visible.sync="dialogWsMemberUpdateVisible" width="30%"
:destroy-on-close="true"
@close="handleClose">
<el-form :model="memberForm" label-position="right" label-width="100px" size="small" ref="updateUserForm">

View File

@ -45,6 +45,7 @@
</el-card>
<el-dialog
:close-on-click-modal="false"
:title="$t('test_resource_pool.create_resource_pool')"
:visible.sync="createVisible" width="70%"
@closed="closeFunc"
@ -109,6 +110,7 @@
</el-dialog>
<el-dialog
:close-on-click-modal="false"
v-loading="result.loading"
:title="$t('test_resource_pool.update_resource_pool')" :visible.sync="updateVisible" width="70%"
:destroy-on-close="true"

View File

@ -50,7 +50,7 @@
</el-card>
<!--Create user-->
<el-dialog :title="$t('user.create')" :visible.sync="createVisible" width="35%" @closed="handleClose"
<el-dialog :close-on-click-modal="false" :title="$t('user.create')" :visible.sync="createVisible" width="35%" @closed="handleClose"
:destroy-on-close="true">
<el-form :model="form" label-position="right" label-width="120px" size="small" :rules="rule" ref="createUserForm">
<el-form-item label="ID" prop="id">
@ -163,7 +163,7 @@
</el-dialog>
<!--Modify user information in system settings-->
<el-dialog :title="$t('user.modify')" :visible.sync="updateVisible" width="35%" :destroy-on-close="true"
<el-dialog :close-on-click-modal="false" :title="$t('user.modify')" :visible.sync="updateVisible" width="35%" :destroy-on-close="true"
@close="handleClose" v-loading="result.loading">
<el-form :model="form" label-position="right" label-width="120px" size="small" :rules="rule" ref="updateUserForm">
<el-form-item label="ID" prop="id">
@ -268,7 +268,7 @@
</template>
</el-dialog>
<!--Changing user password in system settings-->
<el-dialog :title="$t('member.edit_password')" :visible.sync="editPasswordVisible" width="30%"
<el-dialog :close-on-click-modal="false" :title="$t('member.edit_password')" :visible.sync="editPasswordVisible" width="30%"
:destroy-on-close="true" @close="handleClose" left>
<el-form :model="ruleForm" label-position="right" label-width="120px" size="small" :rules="rule"
ref="editPasswordForm" class="demo-ruleForm">

View File

@ -26,7 +26,7 @@
:total="total"/>
</el-card>
<el-dialog :title="$t('member.create')" :visible.sync="createVisible" width="30%" :destroy-on-close="true"
<el-dialog :close-on-click-modal="false" :title="$t('member.create')" :visible.sync="createVisible" width="30%" :destroy-on-close="true"
@close="handleClose">
<el-form :model="form" ref="form" :rules="rules" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.member')" prop="memberSign" :rules="{required: true, message: $t('member.input_id_or_email'), trigger: 'change'}">
@ -65,7 +65,7 @@
</template>
</el-dialog>
<el-dialog :title="$t('member.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true"
<el-dialog :close-on-click-modal="false" :title="$t('member.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true"
@close="handleClose">
<el-form :model="form" label-position="right" label-width="100px" size="small" ref="updateUserForm">
<el-form-item label="ID" prop="id">

View File

@ -2,9 +2,10 @@
<div>
<el-dialog @close="close"
<el-dialog :close-on-click-modal="false"
@close="close"
:title="operationType == 'edit' ? ( readOnly ? $t('test_track.case.view_case') : $t('test_track.case.edit_case')) : $t('test_track.case.create')"
:visible.sync="dialogFormVisible" width="65%" :close-on-click-modal="false">
:visible.sync="dialogFormVisible" width="65%">
<el-form :model="form" :rules="rules" ref="caseFrom" v-loading="result.loading">
@ -268,8 +269,8 @@ export default {
type: [{required: true, message: this.$t('test_track.case.input_type'), trigger: 'change'}],
testId: [{required: true, message: this.$t('commons.please_select'), trigger: 'change'}],
method: [{required: true, message: this.$t('test_track.case.input_method'), trigger: 'change'}],
prerequisite: [{max: 300, message: this.$t('test_track.length_less_than') + '300', trigger: 'blur'}],
remark: [{max: 300, message: this.$t('test_track.length_less_than') + '300', trigger: 'blur'}]
prerequisite: [{max: 500, message: this.$t('test_track.length_less_than') + '500', trigger: 'blur'}],
remark: [{max: 500, message: this.$t('test_track.length_less_than') + '500', trigger: 'blur'}]
},
formLabelWidth: "120px",
operationType: '',
@ -351,8 +352,8 @@ export default {
handleAddStep(index, data) {
let step = {};
step.num = data.num + 1;
step.desc = null;
step.result = null;
step.desc = "";
step.result = "";
this.form.steps.forEach(step => {
if (step.num > data.num) {
step.num++;

View File

@ -1,7 +1,7 @@
<template>
<ms-container>
<ms-main-container>
<el-row>
<el-row :gutter="20">
<el-col :span="15">
<related-test-plan-list ref="relatedTestPlanList"/>
</el-col>
@ -14,33 +14,34 @@
</template>
<script>
import RelatedTestPlanList from "./components/RelatedTestPlanList";
import TestCaseSideList from "./components/TestCaseSideList";
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
export default {
name: "TrackHome",
components: {MsMainContainer, MsContainer, TestCaseSideList, RelatedTestPlanList},
watch: {
'$route'(to,from) {
if (to.path.indexOf('/track/home') > -1) {
this.innitData();
}
}
},
methods: {
innitData() {
this.$refs.relatedTestPlanList.initTableData();
this.$refs.testCaseRecentList.initTableData();
}
import RelatedTestPlanList from "./components/RelatedTestPlanList";
import TestCaseSideList from "./components/TestCaseSideList";
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
export default {
name: "TrackHome",
components: {MsMainContainer, MsContainer, TestCaseSideList, RelatedTestPlanList},
watch: {
'$route'(to, from) {
if (to.path.indexOf('/track/home') > -1) {
this.innitData();
}
}
},
methods: {
innitData() {
this.$refs.relatedTestPlanList.initTableData();
this.$refs.testCaseRecentList.initTableData();
}
}
}
</script>
<style scoped>
.ms-main-container >>> .el-table {
cursor:pointer;
}
.ms-main-container >>> .el-table {
cursor: pointer;
}
</style>

View File

@ -1,8 +1,8 @@
<template>
<div class="track-home-component">
<div>
<el-card>
<template v-slot:header>
<span class="title">{{title}}</span>
<span class="title">{{ title }}</span>
</template>
<slot></slot>
</el-card>
@ -10,27 +10,19 @@
</template>
<script>
export default {
name: "HomeBaseComponent",
props: {
title: {
type: String,
default() {
this.$t('commons.title')
}
}
export default {
name: "HomeBaseComponent",
props: {
title: {
type: String,
default() {
this.$t('commons.title')
}
}
}
}
</script>
<style scoped>
.el-card {
padding: 15px;
}
.track-home-component {
padding: 0 15px 15px;
}
</style>

View File

@ -2,7 +2,8 @@
<div>
<el-dialog :title="operationType == 'edit' ? $t('test_track.plan.edit_plan') : $t('test_track.plan.create_plan')"
<el-dialog :close-on-click-modal="false"
:title="operationType == 'edit' ? $t('test_track.plan.edit_plan') : $t('test_track.plan.create_plan')"
:visible.sync="dialogFormVisible"
@close="close"
width="65%">

View File

@ -23,22 +23,22 @@
</template>
<script>
import {Test} from "../../../../../api/test/model/ScenarioModel"
import MsApiScenarioConfig from "../../../../../api/test/components/ApiScenarioConfig";
import MsContainer from "../../../../../common/components/MsContainer";
import MsMainContainer from "../../../../../common/components/MsMainContainer";
import {Test} from "../../../../../api/test/model/ScenarioModel"
import MsApiScenarioConfig from "../../../../../api/test/components/ApiScenarioConfig";
import MsContainer from "../../../../../common/components/MsContainer";
import MsMainContainer from "../../../../../common/components/MsMainContainer";
export default {
name: "ApiTestDetail",
components: {MsMainContainer, MsContainer, MsApiScenarioConfig},
props: {
id: String,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
export default {
name: "ApiTestDetail",
components: {MsMainContainer, MsContainer, MsApiScenarioConfig},
props: {
id: String,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
result: {},
test: new Test(),
@ -92,7 +92,7 @@
<style scoped>
.test-container {
height: calc(100vh - 150px);
height: calc(100vh - 155px);
min-height: 600px;
padding: 15px;
}

View File

@ -30,24 +30,24 @@
<script>
import MsScenarioResult from "../../../../../api/report/components/ScenarioResult";
import MsMetricChart from "../../../../../api/report/components/MetricChart";
import MsScenarioResults from "../../../../../api/report/components/ScenarioResults";
import MsRequestResult from "../../../../../api/report/components/RequestResult";
import MsContainer from "../../../../../common/components/MsContainer";
import MsMainContainer from "../../../../../common/components/MsMainContainer";
import MsScenarioResult from "../../../../../api/report/components/ScenarioResult";
import MsMetricChart from "../../../../../api/report/components/MetricChart";
import MsScenarioResults from "../../../../../api/report/components/ScenarioResults";
import MsRequestResult from "../../../../../api/report/components/RequestResult";
import MsContainer from "../../../../../common/components/MsContainer";
import MsMainContainer from "../../../../../common/components/MsMainContainer";
export default {
name: "ApiTestResult",
components: {MsMainContainer, MsContainer, MsRequestResult, MsScenarioResults, MsMetricChart, MsScenarioResult},
data() {
return {
activeName: "total",
content: {},
report: {},
loading: true,
fails: [],
}
export default {
name: "ApiTestResult",
components: {MsMainContainer, MsContainer, MsRequestResult, MsScenarioResults, MsMetricChart, MsScenarioResult},
data() {
return {
activeName: "total",
content: {},
report: {},
loading: true,
fails: [],
}
},
props:['reportId'],
watch: {
@ -108,7 +108,7 @@
<style scoped>
.report-container {
height: calc(100vh - 150px);
height: calc(100vh - 155px);
min-height: 600px;
overflow-y: auto;
}

View File

@ -187,11 +187,14 @@ export default {
api_account: 'API account',
api_password: 'API password',
jira_url: 'JIRA url',
jira_issuetype: 'JIRA issuetype',
input_api_account: 'please enter account',
input_api_password: 'Please enter password',
input_jira_url: 'Please enter Jira address, for example: https://metersphere.atlassian.net/',
input_jira_issuetype: 'Please enter the question type',
use_tip: 'Usage guidelines:',
use_tip_one: 'Basic Auth account information is queried in "Company Management-Security and Integration-Open Platform"',
use_tip_tapd: 'Basic Auth account information is queried in "Company Management-Security and Integration-Open Platform"',
use_tip_jira: 'Jira software server authentication information is account password, Jira software cloud authentication information is account + token (account settings-security-create API token)',
use_tip_two: 'After saving the Basic Auth account information, you need to manually associate the ID/key in the Metersphere project',
link_the_project_now: 'Link the project now',
cancel_edit: 'Cancel edit',
@ -298,6 +301,8 @@ export default {
force_stop_btn: 'Terminating',
stop_btn: 'Graceful shutdown',
not_exist: "Test report does not exist",
batch_delete: "Delete reports in bulk",
delete_batch_confirm: 'Confirm batch delete report',
},
load_test: {
same_project_test: 'Only tests within the same project can be run',
@ -446,6 +451,7 @@ export default {
timeout_config: "Timeout Config",
connect_timeout: "Connect Timeout",
response_timeout: "Response Timeout",
follow_redirects: "Follow Redirects",
body_upload_limit_size: "The file size does not exceed 500 MB",
assertions: {
label: "Assertion",

View File

@ -188,11 +188,14 @@ export default {
api_account: 'API 账号',
api_password: 'API 口令',
jira_url: 'JIRA 地址',
jira_issuetype: '问题类型',
input_api_account: '请输入账号',
input_api_password: '请输入口令',
input_jira_url: '请输入Jira地址https://metersphere.atlassian.net/',
input_jira_issuetype: '请输入问题类型',
use_tip: '使用指引:',
use_tip_one: 'Basic Auth 账号信息在"公司管理-安全与集成-开放平台"中查询',
use_tip_tapd: 'Tapd Basic Auth 账号信息在"公司管理-安全与集成-开放平台"中查询',
use_tip_jira: 'Jira software server 认证信息为 账号密码Jira software cloud 认证信息为 账号+令牌(账户设置-安全-创建API令牌)',
use_tip_two: '保存 Basic Auth 账号信息后,需要在 Metersphere 项目中手动关联 ID/key',
link_the_project_now: '马上关联项目',
cancel_edit: '取消编辑',
@ -298,6 +301,8 @@ export default {
force_stop_btn: '强制停止',
stop_btn: '停止',
not_exist: "测试报告不存在",
batch_delete: "批量删除报告",
delete_batch_confirm: '确认批量删除报告',
},
load_test: {
same_project_test: '只能运行同一项目内的测试',
@ -447,6 +452,7 @@ export default {
timeout_config: "超时设置",
connect_timeout: "连接超时",
response_timeout: "响应超时",
follow_redirects: "跟隨重定向",
body_upload_limit_size: "上传文件大小不能超过 500 MB!",
assertions: {
label: "断言",

View File

@ -186,11 +186,14 @@ export default {
api_account: 'API 賬號',
api_password: 'API 口令',
jira_url: 'JIRA 地址',
jira_issuetype: '問題類型',
input_api_account: '請輸入賬號',
input_api_password: '請輸入口令',
input_jira_url: '請輸入Jira地址https://metersphere.atlassian.net/',
input_jira_issuetype: '請輸入問題類型',
use_tip: '使用指引:',
use_tip_one: 'Basic Auth 賬號信息在"公司管理-安全與集成-開放平台"中查詢',
use_tip_tapd: 'Basic Auth 賬號信息在"公司管理-安全與集成-開放平台"中查詢',
use_tip_jira: 'Jira software server 認證信息為 賬號密碼Jira software cloud 認證信息為 賬號+令牌(賬戶設置-安全-創建API令牌)',
use_tip_two: '保存 Basic Auth 賬號信息後,需要在 Metersphere 項目中手動關聯 ID/key',
link_the_project_now: '馬上關聯項目',
cancel_edit: '取消編輯',
@ -297,6 +300,8 @@ export default {
force_stop_btn: '強制停止',
stop_btn: '停止',
not_exist: "測試報告不存在",
batch_delete: "批量刪除報告",
delete_batch_confirm: '確認批量刪除報告',
},
load_test: {
same_project_test: '只能運行同一項目內的測試',