fix(性能测试): 测试报告中的错误记录支持查看请求返回详情
--story=1013022 --user=宋天阳 【性能测试】测试报告中的错误记录支持查看请求返回详情 https://www.tapd.cn/55049933/s/1422637
This commit is contained in:
parent
46e7557bef
commit
d9c52b4404
|
@ -17,6 +17,7 @@ public enum ReportKeys {
|
|||
ErrorsChart,
|
||||
Errors,
|
||||
ErrorsTop5,
|
||||
ErrorSamples,
|
||||
RequestStatistics,
|
||||
Overview,
|
||||
TimeInfo,
|
||||
|
|
|
@ -10,22 +10,19 @@ import io.metersphere.commons.constants.OperLogModule;
|
|||
import io.metersphere.commons.constants.PermissionConstants;
|
||||
import io.metersphere.commons.utils.PageUtils;
|
||||
import io.metersphere.commons.utils.Pager;
|
||||
import io.metersphere.dto.LogDetailDTO;
|
||||
import io.metersphere.dto.ReportDTO;
|
||||
import io.metersphere.dto.*;
|
||||
import io.metersphere.log.annotation.MsAuditLog;
|
||||
import io.metersphere.notice.annotation.SendNotice;
|
||||
import io.metersphere.dto.*;
|
||||
import io.metersphere.request.DeleteReportRequest;
|
||||
import io.metersphere.request.RenameReportRequest;
|
||||
import io.metersphere.request.ReportRequest;
|
||||
import io.metersphere.dto.LoadTestExportJmx;
|
||||
import io.metersphere.service.PerformanceReportService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
|
@ -86,6 +83,11 @@ public class PerformanceReportController {
|
|||
return performanceReportService.getReportErrorsTOP5(reportId);
|
||||
}
|
||||
|
||||
@GetMapping("/content/errors_samples/{reportId}")
|
||||
public SamplesRecord getErrorSamples(@PathVariable String reportId) {
|
||||
return performanceReportService.getErrorSamples(reportId);
|
||||
}
|
||||
|
||||
@GetMapping("/content/testoverview/{reportId}")
|
||||
public TestOverview getTestOverview(@PathVariable String reportId) {
|
||||
return performanceReportService.getTestOverview(reportId);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -2,21 +2,18 @@ package io.metersphere.service;
|
|||
|
||||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.*;
|
||||
import io.metersphere.base.mapper.ext.ExtFileContentMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtLoadTestReportMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtTestPlanLoadCaseMapper;
|
||||
import io.metersphere.commons.constants.PerformanceTestStatus;
|
||||
import io.metersphere.commons.constants.ReportKeys;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.commons.utils.JSON;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.dto.*;
|
||||
import io.metersphere.engine.Engine;
|
||||
import io.metersphere.engine.EngineFactory;
|
||||
import io.metersphere.environment.service.BaseEnvironmentService;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.log.utils.ReflexObjectUtil;
|
||||
import io.metersphere.log.vo.DetailColumn;
|
||||
import io.metersphere.log.vo.OperatingLogDetails;
|
||||
|
@ -27,20 +24,17 @@ import io.metersphere.request.DeleteReportRequest;
|
|||
import io.metersphere.request.OrderRequest;
|
||||
import io.metersphere.request.RenameReportRequest;
|
||||
import io.metersphere.request.ReportRequest;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.ibatis.session.SqlSession;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
@ -273,6 +267,14 @@ public class PerformanceReportService {
|
|||
return JSON.parseArray(content, ErrorsTop5.class);
|
||||
}
|
||||
|
||||
public SamplesRecord getErrorSamples(String id) {
|
||||
String content = getContent(id, ReportKeys.ErrorSamples);
|
||||
if (StringUtils.isEmpty(content)) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parseObject(content, SamplesRecord.class);
|
||||
}
|
||||
|
||||
public TestOverview getTestOverview(String id) {
|
||||
if (isReportError(id)) {
|
||||
return new TestOverview();
|
||||
|
|
|
@ -108,6 +108,10 @@ export function getPerformanceReportErrorsTop5(reportId) {
|
|||
return get('/performance/report/content/errors_top5/' + reportId);
|
||||
}
|
||||
|
||||
export function getPerformanceReportErrorSamples(reportId) {
|
||||
return get('/performance/report/content/errors_samples/' + reportId);
|
||||
}
|
||||
|
||||
export function getSharePerformanceReportErrorsTop5(shareId, reportId) {
|
||||
return get('/share/performance/report/content/errors_top5/' + shareId + '/' + reportId);
|
||||
}
|
||||
|
|
|
@ -102,9 +102,15 @@
|
|||
<el-tab-pane :label="$t('report.test_request_statistics')">
|
||||
<ms-report-request-statistics :report="report" 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" ref="errorLog"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('report.test_log_details')">
|
||||
<ms-report-log-details :report="report"/>
|
||||
</el-tab-pane>
|
||||
|
@ -157,6 +163,7 @@ import MonitorCard from "./components/MonitorCard";
|
|||
import MsTestConfiguration from "./components/TestConfiguration";
|
||||
import {generateShareInfoWithExpired, getShareRedirectUrl} from "@/api/share";
|
||||
import ProjectEnvironmentDialog from "./components/ProjectEnvironmentDialog";
|
||||
import SamplesTabs from "@/business/report/components/samples/SamplesTabs.vue";
|
||||
import {
|
||||
downloadZip,
|
||||
getProjectApplication,
|
||||
|
@ -167,6 +174,7 @@ import {
|
|||
stopTest
|
||||
} from "@/api/report";
|
||||
import {getTest, runTest} from "@/api/performance";
|
||||
import {getPerformanceReportErrorSamples} from "@/api/load-test";
|
||||
|
||||
|
||||
export default {
|
||||
|
@ -184,7 +192,8 @@ export default {
|
|||
MsMainContainer,
|
||||
MsReportTestDetails,
|
||||
MsTag,
|
||||
ProjectEnvironmentDialog
|
||||
ProjectEnvironmentDialog,
|
||||
SamplesTabs
|
||||
},
|
||||
props: {},
|
||||
inject: [
|
||||
|
@ -229,6 +238,8 @@ export default {
|
|||
],
|
||||
testDeleted: false,
|
||||
shareUrl: "",
|
||||
haveErrorSamples: false,
|
||||
errorSamples: {},
|
||||
application: {}
|
||||
};
|
||||
},
|
||||
|
@ -461,6 +472,17 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
checkSampleResults(reportId) {
|
||||
getPerformanceReportErrorSamples(reportId)
|
||||
.then(res => {
|
||||
if (res.data) {
|
||||
this.errorSamples = res.data;
|
||||
this.haveErrorSamples = true;
|
||||
} else {
|
||||
this.haveErrorSamples = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
getReport(reportId) {
|
||||
this.loading = getReport(reportId)
|
||||
.then(res => {
|
||||
|
@ -523,6 +545,7 @@ export default {
|
|||
this.reportId = this.perReportId;
|
||||
}
|
||||
this.getReport(this.reportId);
|
||||
this.checkSampleResults(this.reportId);
|
||||
this.$EventBus.$on('projectChange', this.handleProjectChange);
|
||||
this.getProjectApplication();
|
||||
},
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
<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="errors"
|
||||
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 "@/business/report/components/samples/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];
|
||||
console.info(drawerSamples);
|
||||
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('performance_test.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 "@/business/report/components/samples/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('performance_test.error_samples')" name="errorSample">
|
||||
<error-samples-table :error-samples="errorSamples"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('performance_test.all_samples')" name="allSample">
|
||||
<error-samples-table :error-samples="samples"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ErrorSamplesTable from "@/business/report/components/samples/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,260 @@
|
|||
<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();
|
||||
this.$refs.codeEdit?.$parent?.$parent?.$parent?.$parent?.$parent?.$el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'center',
|
||||
});
|
||||
}, 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>
|
|
@ -11,7 +11,10 @@ const message = {
|
|||
max_current_threads_tips: 'Exceeded the maximum concurrent number of this node {0}',
|
||||
sync_scenario_no_permission_tips: 'No permission to create the scenario cannot perform synchronization',
|
||||
basic_config_file_limit_tip: 'Note: The maximum number of resource files is limited to 10',
|
||||
edit_performance_test_tips: 'No permission to edit test, please check it before operation'
|
||||
edit_performance_test_tips: 'No permission to edit test, please check it before operation',
|
||||
error_samples: 'Error samples',
|
||||
all_samples: 'All samples',
|
||||
response_3_samples: 'The first three pieces of data',
|
||||
}
|
||||
}
|
||||
export default {
|
||||
|
|
|
@ -11,7 +11,10 @@ const message = {
|
|||
max_current_threads_tips: '超出此节点{0}最大并发数',
|
||||
sync_scenario_no_permission_tips: '没有创建接口的权限无法执行同步',
|
||||
basic_config_file_limit_tip: '注:资源文件数最大限制为10个',
|
||||
edit_performance_test_tips: '没有编辑性能测试的权限,请勾选后再操作'
|
||||
edit_performance_test_tips: '没有编辑性能测试的权限,请勾选后再操作',
|
||||
error_samples: '错误请求',
|
||||
all_samples: '所有请求',
|
||||
response_3_samples: '默认抽样前3个请求的响应数据',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,10 @@ const message = {
|
|||
max_current_threads_tips: '超出此節點{0}最大並發數',
|
||||
sync_scenario_no_permission_tips: '沒有创建接口的權限無法執行同步',
|
||||
basic_config_file_limit_tip: '注:資源文件數最大限制為10個',
|
||||
edit_performance_test_tips: '沒有編輯性能測試的權限,請勾選後再操作'
|
||||
edit_performance_test_tips: '沒有編輯性能測試的權限,請勾選後再操作',
|
||||
error_samples: '錯誤請求',
|
||||
all_samples: '所有請求',
|
||||
response_3_samples: '默认抽样前3个请求的响应数据',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue