feat(测试跟踪): 测试跟踪内的性能测试报告支持查看前三个请求
This commit is contained in:
parent
cf99cf7f66
commit
6b8e1714c9
|
@ -1,6 +1,7 @@
|
|||
package io.metersphere.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -13,6 +14,10 @@ public class ResourcePoolOperationInfo {
|
|||
Map<String, NodeOperationInfo> nodeOperationInfos = new HashMap<>();
|
||||
|
||||
public void addNodeOperationInfo(String taskResourceId, String ip, String port, String cpuUsage, int runningTask) {
|
||||
if(StringUtils.isBlank(cpuUsage)) {
|
||||
//节点下如果获取不到cpu使用率,判断为没有查询到该节点的数据。
|
||||
return;
|
||||
}
|
||||
NodeOperationInfo nodeOperationInfo = new NodeOperationInfo();
|
||||
nodeOperationInfo.setIp(ip);
|
||||
nodeOperationInfo.setPort(port);
|
||||
|
|
|
@ -84,7 +84,7 @@ public class PrometheusService {
|
|||
}
|
||||
|
||||
String cpuUsage = null;
|
||||
int runningTask = 0;
|
||||
int runningTask = -1;
|
||||
|
||||
for (TestResource testResource : testResourcePoolDTO.getResources()) {
|
||||
String config = testResource.getConfiguration();
|
||||
|
@ -97,7 +97,9 @@ public class PrometheusService {
|
|||
String cpuUsageQL = this.generatePromQL(new String[]{"system_cpu_usage"}, nodeId);
|
||||
LogUtil.debug(host + "/api/v1/query?query=" + cpuUsageQL);
|
||||
String cpuUsageDouble = this.runPromQL(headers, host, cpuUsageQL);
|
||||
cpuUsage = decimalFormat.format(Double.parseDouble(cpuUsageDouble) * 100) + "%";
|
||||
if(StringUtils.isNotBlank(cpuUsageDouble)){
|
||||
cpuUsage = decimalFormat.format(Double.parseDouble(cpuUsageDouble) * 100) + "%";
|
||||
}
|
||||
}
|
||||
|
||||
// 查询任务数
|
||||
|
|
|
@ -132,6 +132,13 @@ public class ShareController {
|
|||
return performanceReportService.getReportErrorsTOP5(reportId);
|
||||
}
|
||||
|
||||
@GetMapping("/performance/report/content/errors_samples/{shareId}/{reportId}")
|
||||
public SamplesRecord getErrorSamples(@PathVariable String shareId, @PathVariable String reportId) {
|
||||
shareInfoService.validateExpired(shareId);
|
||||
return performanceReportService.getErrorSamples(reportId);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/performance/report/log/resource/{shareId}/{reportId}")
|
||||
public List<LogDetailDTO> getResourceIds(@PathVariable String shareId, @PathVariable String reportId) {
|
||||
shareInfoService.validateExpired(shareId);
|
||||
|
|
|
@ -44,6 +44,7 @@ public class TestPlanLoadCaseDTO extends TestPlanLoadCaseWithBLOBs {
|
|||
private List<LogDetailDTO> reportLogResource;
|
||||
private List<Monitor> reportResource;
|
||||
private List<MetricData> metricData;
|
||||
private SamplesRecord errorSamples;
|
||||
private List<TestResourcePoolDTO> resourcePools;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -605,7 +605,8 @@ public class TestPlanLoadCaseService {
|
|||
response.setReportErrors(reportErrors);
|
||||
List<ErrorsTop5> reportErrorsTop5 = performanceReportService.getReportErrorsTOP5(reportId);
|
||||
response.setReportErrorsTop5(reportErrorsTop5);
|
||||
|
||||
SamplesRecord samplesRecord = performanceReportService.getErrorSamples(reportId);
|
||||
response.setErrorSamples(samplesRecord);
|
||||
// 日志详情
|
||||
List<LogDetailDTO> reportLogResource = performanceReportService.getReportLogResource(reportId);
|
||||
if (org.apache.commons.collections.CollectionUtils.isNotEmpty(reportLogResource)) {
|
||||
|
|
|
@ -112,6 +112,9 @@ export function getPerformanceReportErrorSamples(reportId) {
|
|||
return get('/performance/report/content/errors_samples/' + reportId);
|
||||
}
|
||||
|
||||
export function getSharePerformanceReportErrorSamples(shareId, reportId) {
|
||||
return get('/share/performance/report/content/errors_samples/' + shareId + '/' + reportId);
|
||||
}
|
||||
export function getSharePerformanceReportErrorsTop5(shareId, reportId) {
|
||||
return get('/share/performance/report/content/errors_top5/' + shareId + '/' + reportId);
|
||||
}
|
||||
|
|
|
@ -121,10 +121,17 @@
|
|||
:plan-report-template="planReportTemplate"
|
||||
:share-id="shareId" ref="requestStatistics"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('report.test_error_log')">
|
||||
|
||||
<el-tab-pane v-if="haveErrorSamples" :label="$t('report.test_error_log')">
|
||||
<samples-tabs :samples="errorSamples" ref="errorSamples"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane v-else :label="$t('report.test_error_log')">
|
||||
<ms-report-error-log :report="report" :is-share="isShare" :plan-report-template="planReportTemplate"
|
||||
:share-id="shareId" ref="errorLog"/>
|
||||
</el-tab-pane>
|
||||
|
||||
|
||||
<el-tab-pane :label="$t('report.test_log_details')">
|
||||
<ms-report-log-details :report="report" :is-share="isShare" :plan-report-template="planReportTemplate"
|
||||
:share-id="shareId"/>
|
||||
|
@ -178,14 +185,22 @@ import MonitorCard from "../../../../business/report/components/MonitorCard";
|
|||
import MsReportTestDetails from '../../../../business/report/components/TestDetails';
|
||||
import ProjectEnvironmentDialog from "../../../../business/report/components/ProjectEnvironmentDialog";
|
||||
import MsTag from "metersphere-frontend/src/components/MsTag";
|
||||
import {getPerformanceReport, getPerformanceReportTime, getSharePerformanceReport, getSharePerformanceReportTime} from "../../../../api/load-test";
|
||||
import {
|
||||
getPerformanceReport,
|
||||
getPerformanceReportErrorSamples,
|
||||
getPerformanceReportTime,
|
||||
getSharePerformanceReport, getSharePerformanceReportErrorSamples,
|
||||
getSharePerformanceReportTime
|
||||
} from "../../../../api/load-test";
|
||||
import MsTestConfiguration from "../../../../business/report/components/TestConfiguration";
|
||||
import {getTestProInfo, stopTest} from "../../../../api/report";
|
||||
import SamplesTabs from "@/business/report/components/samples/SamplesTabs.vue";
|
||||
|
||||
|
||||
export default {
|
||||
name: "LoadCaseReportView",
|
||||
components: {
|
||||
SamplesTabs,
|
||||
MsTestConfiguration,
|
||||
MonitorCard,
|
||||
MsPerformanceReportExport,
|
||||
|
@ -221,8 +236,10 @@ export default {
|
|||
websocket: null,
|
||||
dialogFormVisible: false,
|
||||
reportExportVisible: false,
|
||||
haveErrorSamples: false,
|
||||
testPlan: {testResourcePoolId: null},
|
||||
show: true,
|
||||
errorSamples: {},
|
||||
test: {testResourcePoolId: null},
|
||||
};
|
||||
},
|
||||
|
@ -482,6 +499,7 @@ export default {
|
|||
.then(({data}) => {
|
||||
this.handleInit(data);
|
||||
});
|
||||
this.checkSampleResults(this.reportId);
|
||||
} else {
|
||||
this.loading = getPerformanceReport(this.reportId)
|
||||
.then(({data}) => {
|
||||
|
@ -489,6 +507,17 @@ export default {
|
|||
});
|
||||
}
|
||||
},
|
||||
checkSampleResults(reportId) {
|
||||
getSharePerformanceReportErrorSamples(this.shareId,reportId)
|
||||
.then(res => {
|
||||
if (res.data) {
|
||||
this.errorSamples = res.data;
|
||||
this.haveErrorSamples = true;
|
||||
} else {
|
||||
this.haveErrorSamples = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
handleInit(data) {
|
||||
if (data) {
|
||||
this.allProjectEnvMap = data.projectEnvMap;
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package io.metersphere.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class SamplesRecord {
|
||||
//请求名称 - 错误类型 - 错误请求
|
||||
private Map<String, Map<String, List<RequestResult>>> samples;
|
||||
private Map<String, Map<String, Long>> sampleCount;
|
||||
}
|
|
@ -46,6 +46,7 @@ public class TestPlanLoadCaseDTO extends TestPlanLoadCaseWithBLOBs {
|
|||
private List<LogDetailDTO> reportLogResource;
|
||||
private List<Monitor> reportResource;
|
||||
private List<MetricData> metricData;
|
||||
private SamplesRecord errorSamples;
|
||||
private List<TestResourcePoolDTO> resourcePools;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -359,7 +359,10 @@ public class TestPlanReportService {
|
|||
returnDTO.setUiScenarioIdMap(uiScenarioIdMap);
|
||||
}
|
||||
|
||||
if (runInfoDTO != null && testPlanReport == null) {
|
||||
if (testPlanReport == null) {
|
||||
if(runInfoDTO == null){
|
||||
runInfoDTO = new TestPlanReportRunInfoDTO();
|
||||
}
|
||||
if (!saveRequest.isApiCaseIsExecuting() && !saveRequest.isScenarioIsExecuting()) {
|
||||
//如果没有接口用例以及场景运行,执行配置中所选的资源池配置置空,避免报告显示资源池时给用户造成困扰;
|
||||
runModeConfigDTO.setResourcePoolId(null);
|
||||
|
|
|
@ -11,6 +11,11 @@
|
|||
:label="item.name"
|
||||
:disabled="!item.performance"
|
||||
:value="item.id">
|
||||
<template v-slot>
|
||||
<node-operation-label
|
||||
:nodeName="item.name"
|
||||
:node-operation-info="nodeInfo(item.id)"/>
|
||||
</template>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
@ -253,6 +258,8 @@ import {getQuotaValidResourcePools} from "@/api/remote/resource-pool";
|
|||
import {testPlanLoadCaseGetLoadConfig} from "@/api/remote/plan/test-plan-load-case";
|
||||
import {loadTestGetJmxContent} from "@/api/remote/load/performance";
|
||||
import {findThreadGroup} from "@/business/plan/view/comonents/load/ThreadGroup";
|
||||
import NodeOperationLabel from "metersphere-frontend/src/components/resource-pool/NodeOperationLabel";
|
||||
import {getNodeOperationInfo} from "@/api/project";
|
||||
|
||||
const HANDLER = "handler";
|
||||
const THREAD_GROUP_TYPE = "tgType";
|
||||
|
@ -287,7 +294,7 @@ const hexToRgb = function (hex) {
|
|||
|
||||
export default {
|
||||
name: "PerformanceLoadConfig",
|
||||
components: {MsChart},
|
||||
components: {MsChart,NodeOperationLabel},
|
||||
props: {
|
||||
test: {
|
||||
type: Object
|
||||
|
@ -323,6 +330,7 @@ export default {
|
|||
autoStopDelay: 30,
|
||||
isReadOnly: false,
|
||||
rampUpTimeVisible: true,
|
||||
nodeOperationInfo: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -356,6 +364,20 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
refreshNodeOperation() {
|
||||
let nodeOperationInfoRequest = {nodeIds: []};
|
||||
this.resourcePools.forEach(item => {
|
||||
nodeOperationInfoRequest.nodeIds.push(item.id);
|
||||
});
|
||||
|
||||
getNodeOperationInfo(nodeOperationInfoRequest)
|
||||
.then(response => {
|
||||
this.parseNodeOperationStatus(response.data);
|
||||
});
|
||||
},
|
||||
nodeInfo(nodeId) {
|
||||
return this.nodeOperationInfo[nodeId];
|
||||
},
|
||||
getResourcePools() {
|
||||
this.loading = true;
|
||||
getQuotaValidResourcePools()
|
||||
|
@ -366,9 +388,25 @@ export default {
|
|||
if (response.data.filter(p => p.id === this.resourcePool).length === 0) {
|
||||
this.resourcePool = null;
|
||||
}
|
||||
let nodeOperationInfoRequest = {nodeIds: []};
|
||||
this.resourcePools.forEach(item => {
|
||||
nodeOperationInfoRequest.nodeIds.push(item.id);
|
||||
});
|
||||
|
||||
getNodeOperationInfo(nodeOperationInfoRequest)
|
||||
.then(response => {
|
||||
this.parseNodeOperationStatus(response.data);
|
||||
});
|
||||
|
||||
this.resourcePoolChange();
|
||||
});
|
||||
},
|
||||
parseNodeOperationStatus(nodeOperationData) {
|
||||
this.nodeOperationInfo = {};
|
||||
nodeOperationData.forEach(item => {
|
||||
this.nodeOperationInfo[item.id] = item;
|
||||
});
|
||||
},
|
||||
getLoadConfig() {
|
||||
testPlanLoadCaseGetLoadConfig(this.loadCaseId)
|
||||
.then((response) => {
|
||||
|
|
|
@ -93,7 +93,6 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
getReport(row) {
|
||||
|
||||
if (this.isTemplate) {
|
||||
if (row.response) {
|
||||
this.showResponse = true;
|
||||
|
|
|
@ -64,7 +64,11 @@
|
|||
:plan-report-template="planReportTemplate"
|
||||
:share-id="shareId" ref="requestStatistics"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('report.test_error_log')">
|
||||
<el-tab-pane v-if="haveErrorSamples" :label="$t('report.test_error_log')">
|
||||
<samples-tabs :samples="errorSamples" ref="errorSamples"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane v-else :label="$t('report.test_error_log')">
|
||||
<ms-report-error-log :report="report" :is-share="isShare" :plan-report-template="planReportTemplate"
|
||||
:share-id="shareId" ref="errorLog"/>
|
||||
</el-tab-pane>
|
||||
|
@ -83,9 +87,7 @@
|
|||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
</el-card>
|
||||
|
||||
<project-environment-dialog ref="projectEnvDialog"></project-environment-dialog>
|
||||
</el-main>
|
||||
</ms-container>
|
||||
|
@ -104,9 +106,9 @@ import MsReportTestDetails from './TestDetails';
|
|||
import ProjectEnvironmentDialog from "./ProjectEnvironmentDialog";
|
||||
import MsTag from "metersphere-frontend/src/components/MsTag";
|
||||
import MsTestConfiguration from "./TestConfiguration";
|
||||
import SamplesTabs from "./samples/SamplesTabs";
|
||||
|
||||
|
||||
export default {
|
||||
export default {
|
||||
name: "LoadCaseReportView",
|
||||
components: {
|
||||
MsTestConfiguration,
|
||||
|
@ -120,6 +122,7 @@ export default {
|
|||
MsMainContainer,
|
||||
ProjectEnvironmentDialog,
|
||||
MsTag,
|
||||
SamplesTabs,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -145,6 +148,8 @@ export default {
|
|||
testPlan: {testResourcePoolId: null},
|
||||
show: true,
|
||||
test: {testResourcePoolId: null},
|
||||
haveErrorSamples: false,
|
||||
errorSamples: {},
|
||||
};
|
||||
},
|
||||
props: {
|
||||
|
@ -163,6 +168,16 @@ export default {
|
|||
this.init();
|
||||
}
|
||||
},
|
||||
created(){
|
||||
if (this.planReportTemplate) {
|
||||
if (this.planReportTemplate.errorSamples) {
|
||||
this.errorSamples = this.planReportTemplate.errorSamples;
|
||||
this.haveErrorSamples = true;
|
||||
} else {
|
||||
this.haveErrorSamples = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showProjectEnv() {
|
||||
return this.projectEnvMap && JSON.stringify(this.projectEnvMap) !== '{}';
|
||||
|
@ -253,13 +268,21 @@ export default {
|
|||
});
|
||||
},
|
||||
init() {
|
||||
alert(1);
|
||||
this.clearData();
|
||||
if (this.planReportTemplate) {
|
||||
this.handleInit(this.planReportTemplate);
|
||||
}
|
||||
},
|
||||
|
||||
handleInit(data) {
|
||||
if (data) {
|
||||
if (data.errorSamples) {
|
||||
this.errorSamples = data.errorSamples;
|
||||
this.haveErrorSamples = true;
|
||||
} else {
|
||||
this.haveErrorSamples = false;
|
||||
}
|
||||
this.allProjectEnvMap = data.projectEnvMap;
|
||||
this.isProjectEnvShowMore(data.projectEnvMap);
|
||||
this.status = data.status;
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-table
|
||||
ref="samplesTable"
|
||||
:data="tableData"
|
||||
border
|
||||
header-cell-class-name="sample-table-header"
|
||||
style="width: 100%">
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="Sample"
|
||||
min-width="180">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="count"
|
||||
label="Samples"
|
||||
min-width="180">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="error"
|
||||
label="Errors"
|
||||
min-width="180">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="percentOfErrors"
|
||||
label="% In Errors"
|
||||
min-width="180">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="percentOfSamples"
|
||||
label="% In Samples"
|
||||
min-width="180">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="code"
|
||||
label="Response Code"
|
||||
min-width="180">
|
||||
<template v-slot:default="scope">
|
||||
<span v-if="scope.row.code === '200' " style="color: #44b349">{{ scope.row.code }}</span>
|
||||
<span v-else style="color: #E6113C">{{ scope.row.code }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
:label="$t('commons.operating')"
|
||||
min-width="180">
|
||||
<template v-slot="scope">
|
||||
<el-link @click="openRecord(scope.row)">{{ $t('operating_log.info') }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<samples-drawer ref="sampleDrawer" :samples="samplesRecord"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SamplesDrawer from "./SamplesDrawer.vue";
|
||||
|
||||
export default {
|
||||
name: "ErrorSamplesTable",
|
||||
components: {SamplesDrawer},
|
||||
data() {
|
||||
return {
|
||||
id: '',
|
||||
drawer: false,
|
||||
tableData: [],
|
||||
samplesRecord: [],
|
||||
sampleRows: {},
|
||||
};
|
||||
},
|
||||
comments: {
|
||||
SamplesDrawer
|
||||
},
|
||||
props: ['errorSamples'],
|
||||
created() {
|
||||
this.initTableData();
|
||||
},
|
||||
methods: {
|
||||
initTableData() {
|
||||
if (this.errorSamples && this.errorSamples.sampleCount) {
|
||||
let allSampleCount = 0;
|
||||
let errorCount = 0;
|
||||
for (let sampleName in this.errorSamples.sampleCount) {
|
||||
let sampleCountObj = this.errorSamples.sampleCount[sampleName];
|
||||
let index = 0;
|
||||
for (let code in sampleCountObj) {
|
||||
let codeCount = sampleCountObj[code];
|
||||
let sampleTableData = {};
|
||||
sampleTableData.name = sampleName;
|
||||
sampleTableData.code = code;
|
||||
sampleTableData.count = codeCount;
|
||||
this.tableData.push(sampleTableData);
|
||||
index++;
|
||||
if (code !== '200') {
|
||||
errorCount += codeCount;
|
||||
sampleTableData.error = codeCount;
|
||||
} else {
|
||||
sampleTableData.error = 0;
|
||||
}
|
||||
allSampleCount += codeCount;
|
||||
}
|
||||
this.sampleRows[sampleName] = index;
|
||||
}
|
||||
this.tableData.forEach(item => {
|
||||
item.percentOfErrors = (item.error / errorCount * 100).toFixed(2) + '%';
|
||||
item.percentOfSamples = (item.count / allSampleCount * 100).toFixed(2) + '%';
|
||||
});
|
||||
} else {
|
||||
this.tableData = [];
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs.samplesTable.doLayout();
|
||||
}, 500)
|
||||
},
|
||||
objectSpanMethod({row, column, rowIndex, columnIndex}) {
|
||||
if (columnIndex === 0) {
|
||||
let rowspan = this.sampleRows[row.name];
|
||||
if (rowspan != 0) {
|
||||
this.sampleRows[row.name] = 0;
|
||||
return {
|
||||
rowspan: rowspan,
|
||||
colspan: 1,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
rowspan: 0,
|
||||
colspan: 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
handleClose(done) {
|
||||
done();
|
||||
},
|
||||
openRecord(row) {
|
||||
let drawerSamples = this.errorSamples.samples[row.name][row.code];
|
||||
this.$refs.sampleDrawer.openRecord(drawerSamples);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.el-table :deep(.sample-table-header) {
|
||||
color: #1a1a1a;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,67 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-drawer
|
||||
:visible.sync="drawer"
|
||||
direction="rtl"
|
||||
custom-class="sample-drawer"
|
||||
:size="820"
|
||||
:before-close="handleClose">
|
||||
<template v-slot:title>
|
||||
<span style="color: #1a1a1a; font-size: large;">
|
||||
{{ $t('plan.response_3_samples') }}
|
||||
</span>
|
||||
</template>
|
||||
<div style="margin: 0 10px 0 10px ">
|
||||
<el-collapse v-model="activeName" accordion>
|
||||
<el-collapse-item
|
||||
v-for="(sample, index) in sampleRecord" :key="index" :name="index">
|
||||
<template v-slot:title>
|
||||
<div style="font-size: 16px;color: #783887">
|
||||
<span> {{ sample.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<request-result-tail :report-id="sample.id" :response="sample" ref="debugResult"/>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RequestResultTail from "./compnent/RequestResultTail";
|
||||
import {datetimeFormat} from "fit2cloud-ui/src/filters/time";
|
||||
|
||||
export default {
|
||||
name: "ErrorSamplesTable",
|
||||
components: {RequestResultTail},
|
||||
data() {
|
||||
return {
|
||||
activeName: '1',
|
||||
sampleRecord: [],
|
||||
drawer: false,
|
||||
};
|
||||
},
|
||||
props: ['samples'],
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
datetimeFormat,
|
||||
|
||||
handleClose(done) {
|
||||
done();
|
||||
},
|
||||
openRecord(samples) {
|
||||
this.sampleRecord = [];
|
||||
samples.forEach(sample => {
|
||||
this.sampleRecord.push(sample);
|
||||
});
|
||||
this.drawer = true;
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<div style=" height:calc(100vh - 190px)">
|
||||
<el-tabs v-model="activeName" type="card" class="sample-tabs">
|
||||
<el-tab-pane :label="$t('plan.error_samples')" name="errorSample">
|
||||
<error-samples-table :error-samples="errorSamples"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('plan.all_samples')" name="allSample">
|
||||
<error-samples-table :error-samples="samples"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ErrorSamplesTable from "./ErrorSamplesTable.vue";
|
||||
|
||||
export default {
|
||||
name: "SamplesTabs",
|
||||
components: {ErrorSamplesTable},
|
||||
data() {
|
||||
return {
|
||||
activeName: 'errorSample',
|
||||
errorSamples: {
|
||||
sampleCount: {},
|
||||
samples: {}
|
||||
},
|
||||
};
|
||||
},
|
||||
comments: {
|
||||
ErrorSamplesTable
|
||||
},
|
||||
props: ['samples'],
|
||||
created() {
|
||||
this.initErrorSamples();
|
||||
},
|
||||
activated() {
|
||||
this.initErrorSamples();
|
||||
},
|
||||
methods: {
|
||||
initErrorSamples() {
|
||||
this.errorSamples = {
|
||||
sampleCount: {},
|
||||
samples: {}
|
||||
};
|
||||
if (this.samples && this.samples.sampleCount) {
|
||||
for (let sampleName in this.samples.sampleCount) {
|
||||
let sampleCountObj = this.samples.sampleCount[sampleName];
|
||||
for (let code in sampleCountObj) {
|
||||
if (code !== '200') {
|
||||
if (!this.errorSamples.sampleCount[sampleName]) {
|
||||
this.errorSamples.sampleCount[sampleName] = {};
|
||||
this.errorSamples.samples[sampleName] = {};
|
||||
}
|
||||
this.errorSamples.sampleCount[sampleName][code] = this.samples.sampleCount[sampleName][code] || {};
|
||||
this.errorSamples.samples[sampleName][code] = this.samples.samples[sampleName][code] || [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.sample-tabs :deep(.el-tabs__nav) {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,108 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-table :data="assertions" :row-style="getRowStyle" :header-cell-style="getRowStyle">
|
||||
<el-table-column prop="name" :label="$t('api_report.assertions_name')" width="150" show-overflow-tooltip>
|
||||
<template v-slot:default="scope">
|
||||
<span>{{ !scope.row.name || scope.row.name === 'null' ? '' : scope.row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="content"
|
||||
v-if="showContent"
|
||||
:label="$t('api_report.assertions_content')"
|
||||
width="300"
|
||||
show-overflow-tooltip />
|
||||
<el-table-column prop="message" :label="$t('api_report.assertions_error_message')" />
|
||||
<el-table-column prop="pass" :label="$t('api_report.assertions_is_success')" width="180">
|
||||
<template v-slot:default="{ row }">
|
||||
<el-tag size="mini" type="success" v-if="row.pass"> Success </el-tag>
|
||||
<el-tag size="mini" type="danger" v-else> Error </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="script">
|
||||
<template v-slot:default="{ row }">
|
||||
<div class="assertion-item btn circle" v-if="row.script">
|
||||
<i
|
||||
class="el-icon-view el-button el-button--primary el-button--mini is-circle"
|
||||
circle
|
||||
@click="showPage(row.script)" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-dialog
|
||||
:title="$t('api_test.request.assertions.script')"
|
||||
:visible.sync="visible"
|
||||
width="900px"
|
||||
modal-append-to-body
|
||||
append-to-body>
|
||||
<el-row type="flex" justify="space-between" align="middle" class="quick-script-block">
|
||||
<el-col :span="codeSpan" class="script-content">
|
||||
<ms-code-edit
|
||||
v-if="isCodeEditAlive"
|
||||
:read-only="disabled"
|
||||
:data.sync="scriptContent"
|
||||
theme="eclipse"
|
||||
:modes="['java', 'python']"
|
||||
ref="codeEdit" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsCodeEdit from 'metersphere-frontend/src/components/MsCodeEdit';
|
||||
|
||||
export default {
|
||||
name: 'MsAssertionResults',
|
||||
components: { MsCodeEdit },
|
||||
props: {
|
||||
assertions: Array,
|
||||
showContent: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
disabled: false,
|
||||
codeSpan: 20,
|
||||
isCodeEditAlive: true,
|
||||
scriptContent: '',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getRowStyle() {
|
||||
return { backgroundColor: '#F5F5F5' };
|
||||
},
|
||||
showPage(script) {
|
||||
this.disabled = true;
|
||||
this.visible = true;
|
||||
this.scriptContent = script;
|
||||
this.reload();
|
||||
},
|
||||
reload() {
|
||||
this.isCodeEditAlive = false;
|
||||
this.$nextTick(() => (this.isCodeEditAlive = true));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.assertion-item.btn.circle {
|
||||
text-align: right;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.script-content {
|
||||
height: calc(100vh - 570px);
|
||||
min-height: 440px;
|
||||
}
|
||||
|
||||
.quick-script-block {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,140 @@
|
|||
<template>
|
||||
<editor
|
||||
v-model="formatData"
|
||||
:lang="mode"
|
||||
@init="editorInit"
|
||||
:theme="theme"
|
||||
:height="height"
|
||||
:key="readOnly"
|
||||
ref="msEditor" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatJson, formatXml } from 'metersphere-frontend/src/utils/format-utils';
|
||||
import toDiffableHtml from 'diffable-html';
|
||||
import editor from 'vue2-ace-editor';
|
||||
import 'brace/ext/language_tools'; //language extension prerequisite...
|
||||
import 'brace/mode/text';
|
||||
import 'brace/mode/json';
|
||||
import 'brace/mode/xml';
|
||||
import 'brace/mode/html';
|
||||
import 'brace/mode/java';
|
||||
import 'brace/mode/python';
|
||||
import 'brace/mode/sql';
|
||||
import 'brace/mode/javascript';
|
||||
import 'brace/mode/yaml';
|
||||
import 'brace/theme/chrome';
|
||||
import 'brace/theme/eclipse';
|
||||
import 'brace/snippets/javascript'; //snippet
|
||||
|
||||
export default {
|
||||
name: 'MsCodeEdit',
|
||||
components: { editor },
|
||||
data() {
|
||||
return {
|
||||
formatData: '',
|
||||
};
|
||||
},
|
||||
props: {
|
||||
height: [String, Number],
|
||||
data: {
|
||||
type: String,
|
||||
default() {
|
||||
return '';
|
||||
},
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'chrome';
|
||||
},
|
||||
},
|
||||
init: {
|
||||
type: Function,
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'text';
|
||||
},
|
||||
},
|
||||
modes: {
|
||||
type: Array,
|
||||
default() {
|
||||
return ['text', 'json', 'xml', 'html'];
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.format();
|
||||
},
|
||||
watch: {
|
||||
formatData() {
|
||||
this.$emit('update:data', this.formatData);
|
||||
},
|
||||
mode() {
|
||||
this.format();
|
||||
},
|
||||
data() {
|
||||
this.formatData = this.data;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
insert(code) {
|
||||
if (this.$refs.msEditor.editor) {
|
||||
this.$refs.msEditor.editor.insert(code);
|
||||
}
|
||||
},
|
||||
editorInit: function (editor) {
|
||||
// require('brace/ext/language_tools'); //language extension prerequisite...
|
||||
// require('brace/mode/text');
|
||||
// require('brace/mode/json');
|
||||
// require('brace/mode/xml');
|
||||
// require('brace/mode/html');
|
||||
// require('brace/mode/java');
|
||||
// require('brace/mode/python');
|
||||
// require('brace/mode/sql');
|
||||
// require('brace/mode/javascript');
|
||||
// require('brace/mode/yaml');
|
||||
// // this.modes.forEach((mode) => {
|
||||
// // require('brace/mode/' + mode); //language
|
||||
// // });
|
||||
// require('brace/theme/' + this.theme);
|
||||
// require('brace/snippets/javascript'); //snippet
|
||||
if (this.readOnly) {
|
||||
editor.setReadOnly(true);
|
||||
}
|
||||
if (this.init) {
|
||||
this.init(editor);
|
||||
}
|
||||
},
|
||||
format() {
|
||||
switch (this.mode) {
|
||||
case 'json':
|
||||
this.formatData = formatJson(this.data);
|
||||
break;
|
||||
case 'html':
|
||||
this.formatData = toDiffableHtml(this.data);
|
||||
break;
|
||||
case 'xml':
|
||||
this.formatData = formatXml(this.data);
|
||||
break;
|
||||
default:
|
||||
if (this.data) {
|
||||
this.formatData = this.data;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,58 @@
|
|||
<template>
|
||||
<el-dropdown @command="handleCommand" class="ms-dropdown">
|
||||
<slot>
|
||||
<span class="el-dropdown-link">
|
||||
{{ currentCommand }}
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</span>
|
||||
</slot>
|
||||
<el-dropdown-menu slot="dropdown" chang>
|
||||
<el-dropdown-item v-for="(command, index) in commands" :key="index" :command="command">
|
||||
{{ command }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MsDropdown',
|
||||
data() {
|
||||
return {
|
||||
currentCommand: '',
|
||||
};
|
||||
},
|
||||
props: {
|
||||
commands: {
|
||||
type: Array,
|
||||
},
|
||||
defaultCommand: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (this.defaultCommand) {
|
||||
this.currentCommand = this.defaultCommand;
|
||||
} else if (this.commands && this.commands.length > 0) {
|
||||
this.currentCommand = this.commands[0];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCommand(command) {
|
||||
this.currentCommand = command;
|
||||
this.$emit('command', command);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-dropdown-link {
|
||||
cursor: pointer;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.el-icon-arrow-down {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,133 @@
|
|||
<template>
|
||||
<div class="metric-container">
|
||||
<el-row type="flex">
|
||||
<el-col>
|
||||
<div style="font-size: 14px; color: #aaaaaa; float: left">{{ $t('api_report.response_code') }} :</div>
|
||||
<el-tooltip v-if="responseResult.responseCode" :content="responseResult.responseCode" placement="top">
|
||||
<div
|
||||
v-if="
|
||||
response.attachInfoMap &&
|
||||
response.attachInfoMap.FAKE_ERROR &&
|
||||
response.attachInfoMap.status === 'FAKE_ERROR'
|
||||
"
|
||||
class="node-title"
|
||||
:class="'ms-req-error-report-result'">
|
||||
{{ responseResult && responseResult.responseCode ? responseResult.responseCode : '0' }}
|
||||
</div>
|
||||
<div v-else class="node-title" :class="response && response.success ? 'ms-req-success' : 'ms-req-error'">
|
||||
{{ responseResult && responseResult.responseCode ? responseResult.responseCode : '0' }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<div v-else class="node-title" :class="response && response.success ? 'ms-req-success' : 'ms-req-error'">
|
||||
{{ responseResult && responseResult.responseCode ? responseResult.responseCode : '0' }}
|
||||
</div>
|
||||
<div v-if="response && response.attachInfoMap && response.attachInfoMap.FAKE_ERROR">
|
||||
<div
|
||||
class="node-title ms-req-error-report-result"
|
||||
v-if="response.attachInfoMap.status === 'FAKE_ERROR'"
|
||||
style="margin-left: 0px; padding-left: 0px">
|
||||
{{ response.attachInfoMap.FAKE_ERROR }}
|
||||
</div>
|
||||
<div
|
||||
class="node-title ms-req-success"
|
||||
v-else-if="response.success"
|
||||
style="margin-left: 0px; padding-left: 0px">
|
||||
{{ response.attachInfoMap.FAKE_ERROR }}
|
||||
</div>
|
||||
<div class="node-title ms-req-error" v-else style="margin-left: 0px; padding-left: 0px">
|
||||
{{ response.attachInfoMap.FAKE_ERROR }}
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col>
|
||||
<div style="font-size: 14px; color: #aaaaaa; float: left">{{ $t('api_report.response_time') }} :</div>
|
||||
<div style="font-size: 14px; color: #61c550; margin-top: 2px; margin-left: 10px; float: left">
|
||||
{{ responseResult && responseResult.responseTime ? responseResult.responseTime : 0 }}
|
||||
ms
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col>
|
||||
<div style="font-size: 14px; color: #aaaaaa; float: left">{{ $t('api_report.response_size') }} :</div>
|
||||
<div style="font-size: 14px; color: #61c550; margin-top: 2px; margin-left: 10px; float: left">
|
||||
{{ responseResult && responseResult.responseSize ? responseResult.responseSize : 0 }}
|
||||
bytes
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" style="margin-top: 5px">
|
||||
<el-col v-if="response && response.poolName">
|
||||
<div style="font-size: 14px; color: #aaaaaa; float: left">
|
||||
<span> {{ $t('load_test.select_resource_pool') + ':' }} </span>
|
||||
</div>
|
||||
<div style="font-size: 14px; color: #61c550; margin-left: 10px; float: left">
|
||||
{{ response.poolName }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col type="flex" v-if="response && response.envName">
|
||||
<div style="font-size: 14px; color: #aaaaaa; float: left">
|
||||
<span> {{ $t('commons.environment') + ':' }} </span>
|
||||
</div>
|
||||
<div style="font-size: 14px; color: #61c550; margin-left: 10px; float: left">
|
||||
{{ response.envName }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col></el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MsRequestMetric',
|
||||
|
||||
props: {
|
||||
response: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
responseResult() {
|
||||
return this.response && this.response.responseResult ? this.response.responseResult : {};
|
||||
},
|
||||
error() {
|
||||
return this.response && this.response.responseCode && this.response.responseCode >= 400;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.metric-container {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.node-title {
|
||||
/*width: 150px;*/
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1 1 auto;
|
||||
padding: 0px 5px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
color: #61c550;
|
||||
margin-top: 2px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.ms-req-error {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.ms-req-error-report-result {
|
||||
color: #f6972a;
|
||||
}
|
||||
|
||||
.ms-req-success {
|
||||
color: #67c23a;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<div>
|
||||
<div style="background-color: #fafafa;margin: 0 0 5px 0">
|
||||
<el-tag
|
||||
size="medium"
|
||||
:style="{
|
||||
'background-color': getColor(true, response.method),
|
||||
border: getColor(true, response.method),
|
||||
borderRadius: '0px',
|
||||
marginRight: '20px',
|
||||
color: 'white',
|
||||
}">
|
||||
{{ response.method }}
|
||||
</el-tag>
|
||||
{{ response.url }}
|
||||
</div>
|
||||
<div class="request-result">
|
||||
<ms-request-metric v-if="showMetric" :response="response"/>
|
||||
<ms-response-result
|
||||
:currentProtocol="currentProtocol"
|
||||
:response="response"
|
||||
:isTestPlan="isTestPlan"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsResponseResult from './ResponseResult';
|
||||
import MsRequestMetric from './RequestMetric';
|
||||
|
||||
export default {
|
||||
name: 'MsRequestResultTail',
|
||||
components: {MsRequestMetric, MsResponseResult},
|
||||
props: {
|
||||
response: Object,
|
||||
currentProtocol: String,
|
||||
reportId: String,
|
||||
showMetric: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
isTestPlan: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
report: {},
|
||||
apiMethodColor: {
|
||||
'GET': '#61AFFE',
|
||||
'POST': '#49CC90',
|
||||
'PUT': '#fca130',
|
||||
'PATCH': '#E2EE11',
|
||||
'DELETE': '#f93e3d',
|
||||
'OPTIONS': '#0EF5DA',
|
||||
'HEAD': '#8E58E7',
|
||||
'CONNECT': '#90AFAE',
|
||||
'DUBBO': '#C36EEF',
|
||||
'dubbo://': '#C36EEF',
|
||||
'SQL': '#0AEAD4',
|
||||
'TCP': '#0A52DF',
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getColor(enable, method) {
|
||||
return this.apiMethodColor[method];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-result {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.request-result .info {
|
||||
background-color: #f9f9f9;
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.request-result .method {
|
||||
color: #1e90ff;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.request-result .url {
|
||||
color: #7f7f7f;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-top: 4px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.request-result .tab .el-tabs__header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.request-result .text {
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sub-result .info {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.sub-result .method {
|
||||
border-left: 5px solid #1e90ff;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.sub-result:last-child {
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.request-result .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,314 @@
|
|||
<template>
|
||||
<div class="text-container" v-if="responseResult">
|
||||
<el-tabs v-model="activeName" v-show="isActive" class="response-result">
|
||||
<el-tab-pane
|
||||
:label="$t('api_test.definition.request.response_body')"
|
||||
name="body"
|
||||
class="pane"
|
||||
>
|
||||
<ms-sql-result-table
|
||||
v-if="isSqlType && activeName === 'body'"
|
||||
:body="responseResult.body"
|
||||
/>
|
||||
<ms-code-edit
|
||||
v-if="!isSqlType && isMsCodeEditShow && activeName === 'body'"
|
||||
:mode="mode"
|
||||
:read-only="true"
|
||||
:modes="modes"
|
||||
:data.sync="responseResult.body"
|
||||
height="250px"
|
||||
ref="codeEdit"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane
|
||||
:label="$t('api_test.definition.request.response_header')"
|
||||
name="headers"
|
||||
class="pane"
|
||||
>
|
||||
<ms-code-edit
|
||||
:mode="'text'"
|
||||
:read-only="true"
|
||||
:data.sync="responseResult.headers"
|
||||
ref="codeEdit"
|
||||
v-if="activeName === 'headers'"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane
|
||||
v-if="responseResult.console"
|
||||
:label="$t('api_test.definition.request.console')"
|
||||
name="console"
|
||||
class="pane"
|
||||
>
|
||||
<ms-code-edit
|
||||
:mode="'text'"
|
||||
:read-only="true"
|
||||
:data.sync="responseResult.console"
|
||||
ref="codeEdit"
|
||||
v-if="activeName === 'console'"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane
|
||||
:label="$t('api_report.assertions')"
|
||||
name="assertions"
|
||||
class="pane assertions"
|
||||
>
|
||||
<ms-assertion-results
|
||||
:assertions="responseResult.assertions"
|
||||
v-if="activeName === 'assertions'"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane
|
||||
:label="$t('api_test.request.extract.label')"
|
||||
name="label"
|
||||
class="pane"
|
||||
>
|
||||
<ms-code-edit
|
||||
:mode="'text'"
|
||||
:read-only="true"
|
||||
:data.sync="responseResult.vars"
|
||||
v-if="activeName === 'label'"
|
||||
ref="codeEdit"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane
|
||||
:label="$t('api_report.request_body')"
|
||||
name="request_body"
|
||||
class="pane"
|
||||
>
|
||||
<ms-code-edit
|
||||
:mode="'text'"
|
||||
:read-only="true"
|
||||
:data.sync="reqMessages"
|
||||
v-if="activeName === 'request_body'"
|
||||
ref="codeEdit"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane
|
||||
v-if="activeName == 'body'"
|
||||
:disabled="true"
|
||||
name="mode"
|
||||
class="pane cookie"
|
||||
>
|
||||
<template v-slot:label>
|
||||
<ms-dropdown
|
||||
v-if="currentProtocol === 'SQL'"
|
||||
:commands="sqlModes"
|
||||
:default-command="mode"
|
||||
@command="sqlModeChange"
|
||||
/>
|
||||
<ms-dropdown
|
||||
v-else
|
||||
:commands="modes"
|
||||
:default-command="mode"
|
||||
@command="modeChange"
|
||||
ref="modeDropdown"
|
||||
/>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsAssertionResults from "./AssertionResults";
|
||||
import MsCodeEdit from "./MsCodeEdit";
|
||||
import MsDropdown from "./MsDropdown";
|
||||
import MsSqlResultTable from "./SqlResultTable";
|
||||
|
||||
export default {
|
||||
name: "MsResponseResult",
|
||||
|
||||
components: {
|
||||
MsDropdown,
|
||||
MsCodeEdit,
|
||||
MsAssertionResults,
|
||||
MsSqlResultTable,
|
||||
},
|
||||
|
||||
props: {
|
||||
response: Object,
|
||||
currentProtocol: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: true,
|
||||
activeName: "body",
|
||||
modes: ["text", "json", "xml", "html"],
|
||||
sqlModes: ["text", "table"],
|
||||
bodyFormat: {
|
||||
TEXT: "text",
|
||||
JSON: "json",
|
||||
XML: "xml",
|
||||
HTML: "html",
|
||||
},
|
||||
mode: "text",
|
||||
isMsCodeEditShow: true,
|
||||
reqMessages: "",
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
response() {
|
||||
this.setBodyType();
|
||||
this.setReqMessage();
|
||||
},
|
||||
activeName: {
|
||||
handler() {
|
||||
setTimeout(() => {
|
||||
// 展开动画大概是 300ms 左右,使视觉效果更流畅
|
||||
this.$refs.codeEdit?.$el.querySelector(".ace_text-input")?.focus();
|
||||
}, 300);
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
modeChange(mode) {
|
||||
this.mode = mode;
|
||||
},
|
||||
sqlModeChange(mode) {
|
||||
this.mode = mode;
|
||||
},
|
||||
setBodyType() {
|
||||
if (
|
||||
this.response &&
|
||||
this.response.responseResult &&
|
||||
this.response.responseResult.headers &&
|
||||
this.response.responseResult.headers.indexOf(
|
||||
"Content-Type: application/json"
|
||||
) > 0
|
||||
) {
|
||||
this.mode = this.bodyFormat.JSON;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.modeDropdown) {
|
||||
this.$refs.modeDropdown.handleCommand(this.bodyFormat.JSON);
|
||||
this.msCodeReload();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
msCodeReload() {
|
||||
this.isMsCodeEditShow = false;
|
||||
this.$nextTick(() => {
|
||||
this.isMsCodeEditShow = true;
|
||||
});
|
||||
},
|
||||
setReqMessage() {
|
||||
if (this.response) {
|
||||
if (!this.response.url) {
|
||||
this.response.url = "";
|
||||
}
|
||||
if (!this.response.headers) {
|
||||
this.response.headers = "";
|
||||
}
|
||||
if (!this.response.cookies) {
|
||||
this.response.cookies = "";
|
||||
}
|
||||
if (!this.response.body) {
|
||||
this.response.body = "";
|
||||
}
|
||||
if (!this.response.responseResult) {
|
||||
this.response.responseResult = {};
|
||||
}
|
||||
if (!this.response.responseResult.vars) {
|
||||
this.response.responseResult.vars = "";
|
||||
}
|
||||
this.reqMessages = "";
|
||||
if (this.response.url) {
|
||||
this.reqMessages +=
|
||||
this.$t("api_test.request.address") +
|
||||
":\n" +
|
||||
this.response.url +
|
||||
"\n";
|
||||
}
|
||||
if (this.response.headers) {
|
||||
this.reqMessages +=
|
||||
this.$t("api_test.scenario.headers") +
|
||||
":\n" +
|
||||
this.response.headers +
|
||||
"\n";
|
||||
}
|
||||
|
||||
if (this.response.cookies) {
|
||||
this.reqMessages += "Cookie:" + this.response.cookies + "\n";
|
||||
}
|
||||
this.reqMessages += "Body:" + "\n" + this.response.body;
|
||||
if (this.mode === this.bodyFormat.JSON) {
|
||||
this.msCodeReload();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.setBodyType();
|
||||
this.setReqMessage();
|
||||
},
|
||||
computed: {
|
||||
isSqlType() {
|
||||
return (
|
||||
this.currentProtocol === "SQL" &&
|
||||
this.response &&
|
||||
this.response.responseResult &&
|
||||
this.response.responseResult.responseCode === "200" &&
|
||||
this.mode === "table"
|
||||
);
|
||||
},
|
||||
responseResult() {
|
||||
return this.response && this.response.responseResult
|
||||
? this.response.responseResult
|
||||
: {};
|
||||
},
|
||||
},
|
||||
};
|
||||
</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: 1px 0;
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.text-container .pane.cookie {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-wrap::after) {
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.ms-div {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.response-result :deep(.el-tabs__nav) {
|
||||
float: left !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-table
|
||||
v-for="(table, index) in tables"
|
||||
:key="index"
|
||||
:data="table.tableData"
|
||||
border
|
||||
size="mini"
|
||||
highlight-current-row>
|
||||
<el-table-column v-for="(title, index) in table.titles" :key="index" :label="title" min-width="150px">
|
||||
<template v-slot:default="scope">
|
||||
<el-popover placement="top" trigger="click">
|
||||
<el-container>
|
||||
<div>{{ scope.row[title] }}</div>
|
||||
</el-container>
|
||||
<span class="table-content" slot="reference">{{ scope.row[title] }}</span>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MsSqlResultTable',
|
||||
data() {
|
||||
return {
|
||||
tables: [],
|
||||
titles: [],
|
||||
};
|
||||
},
|
||||
props: {
|
||||
body: String,
|
||||
},
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
watch: {
|
||||
body() {
|
||||
this.init();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
if (!this.body) {
|
||||
return;
|
||||
}
|
||||
this.tables = [];
|
||||
this.titles = [];
|
||||
let rowArray = this.body.split('\n');
|
||||
// 过多会有性能问题
|
||||
if (rowArray.length > 100) {
|
||||
rowArray = rowArray.slice(0, 100);
|
||||
}
|
||||
this.getTableData(rowArray);
|
||||
},
|
||||
getTableData(rowArray) {
|
||||
let titles;
|
||||
let result = [];
|
||||
for (let i = 0; i < rowArray.length; i++) {
|
||||
let colArray = rowArray[i].split('\t');
|
||||
if (i === 0) {
|
||||
titles = colArray;
|
||||
} else {
|
||||
if (colArray.length != titles.length) {
|
||||
// 创建新的表
|
||||
if (colArray.length === 1 && colArray[0] === '') {
|
||||
this.getTableData(rowArray.slice(i + 1));
|
||||
} else {
|
||||
this.getTableData(rowArray.slice(i));
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
let item = {};
|
||||
for (let j = 0; j < colArray.length; j++) {
|
||||
item[titles[j]] = colArray[j] ? colArray[j] : '';
|
||||
}
|
||||
// 性能考虑每个表格取值不超过一百
|
||||
if (result.length < 100) {
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.tables.splice(0, 0, {
|
||||
titles: titles,
|
||||
tableData: result,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-table {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.el-table :deep(.cell) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table-content {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.el-container {
|
||||
overflow: auto;
|
||||
max-height: 500px;
|
||||
}
|
||||
</style>
|
|
@ -63,6 +63,9 @@ const message = {
|
|||
}
|
||||
},
|
||||
plan: {
|
||||
error_samples: 'Error samples',
|
||||
all_samples: 'All samples',
|
||||
response_3_samples: 'The first three pieces of data',
|
||||
batch_delete_tip: "Do you want to continue deleting the test plan?",
|
||||
relevance_case_success: "Relevance success"
|
||||
},
|
||||
|
|
|
@ -63,6 +63,9 @@ const message = {
|
|||
},
|
||||
},
|
||||
plan: {
|
||||
error_samples: '错误请求',
|
||||
all_samples: '所有请求',
|
||||
response_3_samples: '默认抽样前3个请求的响应数据',
|
||||
batch_delete_tip: "批量删除测试计划,是否继续?",
|
||||
relevance_case_success: "已添加至测试计划"
|
||||
},
|
||||
|
|
|
@ -63,6 +63,9 @@ const message = {
|
|||
}
|
||||
},
|
||||
plan: {
|
||||
error_samples: '錯誤請求',
|
||||
all_samples: '所有請求',
|
||||
response_3_samples: '默認抽樣前3個請求的響應數據',
|
||||
batch_delete_tip: "批量刪除測試計劃,是否繼續?",
|
||||
relevance_case_success: "已添加至測試計劃"
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue