fix(测试跟踪): 测试计划报告导出UI报告无法展示
This commit is contained in:
parent
09a755ebde
commit
804a9c9037
|
@ -20,7 +20,8 @@ import javax.servlet.http.HttpServletRequest;
|
|||
"/api/project",
|
||||
"/test/case/relevance/api",
|
||||
"/test/case/relevance/scenario",
|
||||
"home"
|
||||
"/share/api/definition",
|
||||
"/home"
|
||||
})
|
||||
public class TrackApiTestController {
|
||||
@Resource
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import {get, post} from "metersphere-frontend/src/plugins/request"
|
||||
|
||||
export function getScenarioReport(reportId) {
|
||||
return reportId ? get('/ui/scenario/report/get/' + reportId) : {};
|
||||
}
|
||||
|
||||
export function getScenarioReportAll(reportId) {
|
||||
return reportId ? get('/ui/scenario/report/getAll/' + reportId) : {};
|
||||
}
|
||||
|
||||
export function getApiReport(testId) {
|
||||
return testId ? get('/api/definition/report/getReport/' + testId) : {};
|
||||
}
|
||||
|
||||
export function getShareApiReport(shareId, testId) {
|
||||
return testId ? get('/share/api/definition/report/getReport/' + shareId + '/' + testId) : {};
|
||||
}
|
||||
|
||||
export function getShareScenarioReport(shareId, reportId) {
|
||||
return reportId ? get('/share/ui/scenario/report/get/' + shareId + '/' + reportId) : {};
|
||||
}
|
|
@ -49,10 +49,11 @@
|
|||
</el-card>
|
||||
</ms-aside-container>
|
||||
<el-main>
|
||||
<micro-app v-if="showResponse"
|
||||
route-name="ApiReportView"
|
||||
service="ui"
|
||||
:route-params="{
|
||||
<div v-if="showResponse">
|
||||
<micro-app v-if="!isTemplate"
|
||||
service="ui"
|
||||
route-name="ApiReportView"
|
||||
:route-params="{
|
||||
reportId,
|
||||
isShare,
|
||||
shareId,
|
||||
|
@ -60,13 +61,23 @@
|
|||
isTemplate,
|
||||
response
|
||||
}"/>
|
||||
|
||||
<UiShareReportDetail
|
||||
v-else
|
||||
:report-id="reportId"
|
||||
:share-id="shareId"
|
||||
:is-share="isShare"
|
||||
:template-report="response"
|
||||
:is-template="true"
|
||||
:is-plan="true"
|
||||
:show-cancel-button="false"/>
|
||||
</div>
|
||||
<div class="empty" v-else>{{ $t('test_track.plan.load_case.content_empty') }}</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UiShareReportDetail from "../ui/UiShareReportDetail"
|
||||
import PriorityTableItem from "../../../../../../common/tableItems/planview/PriorityTableItem";
|
||||
import TypeTableItem from "../../../../../../common/tableItems/planview/TypeTableItem";
|
||||
import MethodTableItem from "../../../../../../common/tableItems/planview/MethodTableItem";
|
||||
|
@ -85,7 +96,8 @@ export default {
|
|||
MsMainContainer,
|
||||
MsAsideContainer,
|
||||
MicroApp,
|
||||
MsTableColumn, MsTable, StatusTableItem, MethodTableItem, TypeTableItem, PriorityTableItem
|
||||
MsTableColumn, MsTable, StatusTableItem, MethodTableItem, TypeTableItem, PriorityTableItem,
|
||||
UiShareReportDetail
|
||||
},
|
||||
props: {
|
||||
planId: String,
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
<template>
|
||||
<header class="report-header">
|
||||
<el-row>
|
||||
<el-col>
|
||||
<span v-if="!debug">
|
||||
<el-input v-if="nameIsEdit" size="mini" @blur="handleSave(report.name)" @keyup.enter.native="handleSaveKeyUp"
|
||||
style="width: 200px" v-model="report.name" maxlength="60" show-word-limit/>
|
||||
<span v-else>
|
||||
<router-link v-if="isSingleScenario"
|
||||
:to="{name: isUi ? 'uiAutomation' : 'ApiAutomation', params: { dataSelectRange: 'edit:' + scenarioId }}">
|
||||
{{ report.name }}
|
||||
</router-link>
|
||||
<span v-else>
|
||||
{{ report.name }}
|
||||
</span>
|
||||
<i v-if="showCancelButton" class="el-icon-edit" style="cursor:pointer" @click="nameIsEdit = true"
|
||||
@click.stop/>
|
||||
</span>
|
||||
</span>
|
||||
<span v-if="report.endTime || report.createTime">
|
||||
<span style="margin-left: 10px">{{ $t('report.test_start_time') }}:</span>
|
||||
<span class="time"> {{ report.createTime | timestampFormatDate }}</span>
|
||||
<span style="margin-left: 10px">{{ $t('report.test_end_time') }}:</span>
|
||||
<span class="time"> {{ report.endTime | timestampFormatDate }}</span>
|
||||
</span>
|
||||
<div style="float: right">
|
||||
<el-button v-if="!isPlan && (!debug || exportFlag) && !isTemplate && !isUi"
|
||||
v-permission="['PROJECT_API_REPORT:READ+EXPORT']" :disabled="isReadOnly" class="export-button"
|
||||
plain type="primary" size="mini" @click="handleExport(report.name)" style="margin-right: 10px">
|
||||
{{ $t('test_track.plan_view.export_report') }}
|
||||
</el-button>
|
||||
|
||||
<el-popover
|
||||
v-if="!isPlan && (!debug || exportFlag) && !isTemplate"
|
||||
v-permission="['PROJECT_PERFORMANCE_REPORT:READ+EXPORT']"
|
||||
style="margin-right: 10px;float: right;"
|
||||
placement="bottom"
|
||||
width="300">
|
||||
<p>{{ shareUrl }}</p>
|
||||
<span style="color: red;float: left;margin-left: 10px;" v-if="application.typeValue">{{
|
||||
$t('commons.validity_period') + application.typeValue
|
||||
}}</span>
|
||||
<div style="text-align: right; margin: 0">
|
||||
<el-button type="primary" size="mini" :disabled="!shareUrl"
|
||||
v-clipboard:copy="shareUrl">{{ $t("commons.copy") }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-button slot="reference" :disabled="isReadOnly" type="danger" plain size="mini"
|
||||
@click="handleShare(report)">
|
||||
{{ $t('test_track.plan_view.share_report') }}
|
||||
</el-button>
|
||||
</el-popover>
|
||||
|
||||
<el-button v-if="showRerunButton" class="rerun-button" plain size="mini" @click="rerun">
|
||||
{{ $t('api_test.automation.rerun') }}
|
||||
</el-button>
|
||||
|
||||
<el-button v-if="showCancelButton" class="export-button" plain size="mini" @click="returnView">
|
||||
{{ $t('commons.cancel') }}
|
||||
</el-button>
|
||||
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-if="showProjectEnv" type="flex">
|
||||
<span> {{ $t('commons.environment') + ':' }} </span>
|
||||
<div v-for="(values,key) in projectEnvMap" :key="key" style="margin-right: 10px">
|
||||
{{ key + ":" }}
|
||||
<ms-tag v-for="(item,index) in values" :key="index" type="success" :content="item"
|
||||
style="margin-left: 2px"/>
|
||||
</div>
|
||||
</el-row>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {generateShareInfoWithExpired, getShareRedirectUrl} from "@/api/share";
|
||||
import MsTag from "metersphere-frontend/src/components/MsTag";
|
||||
import {getCurrentProjectID} from "@/business/utils/sdk-utils";
|
||||
|
||||
export default {
|
||||
name: "MsApiReportViewHeader",
|
||||
components: {MsTag},
|
||||
props: {
|
||||
report: {},
|
||||
projectEnvMap: {},
|
||||
debug: Boolean,
|
||||
showCancelButton: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showRerunButton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isTemplate: Boolean,
|
||||
exportFlag: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isPlan: Boolean
|
||||
},
|
||||
computed: {
|
||||
showProjectEnv() {
|
||||
return this.projectEnvMap && JSON.stringify(this.projectEnvMap) !== '{}';
|
||||
},
|
||||
path() {
|
||||
return "/api/test/edit?id=" + this.report.testId;
|
||||
},
|
||||
scenarioId() {
|
||||
if (typeof this.report.scenarioId === 'string') {
|
||||
return this.report.scenarioId;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
isSingleScenario() {
|
||||
try {
|
||||
JSON.parse(this.report.scenarioId);
|
||||
return false;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
isUi() {
|
||||
return this.report.reportType && this.report.reportType.startsWith("UI");
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isReadOnly: false,
|
||||
nameIsEdit: false,
|
||||
shareUrl: "",
|
||||
application: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
handleExport(name) {
|
||||
this.$emit('reportExport', name);
|
||||
},
|
||||
handleSave(name) {
|
||||
this.nameIsEdit = false;
|
||||
this.$emit('reportSave', name);
|
||||
},
|
||||
handleSaveKeyUp($event) {
|
||||
$event.target.blur();
|
||||
},
|
||||
rerun() {
|
||||
let type = this.report.reportType;
|
||||
let rerunObj = {type: type, reportId: this.report.id}
|
||||
this.$post('/api/test/exec/rerun', rerunObj, res => {
|
||||
if (res.data !== 'SUCCESS') {
|
||||
this.$error(res.data);
|
||||
} else {
|
||||
this.$success(this.$t('api_test.automation.rerun_success'));
|
||||
this.returnView();
|
||||
}
|
||||
});
|
||||
},
|
||||
returnView() {
|
||||
if (this.isUi) {
|
||||
this.$router.push('/ui/report');
|
||||
} else {
|
||||
this.$router.push('/api/automation/report');
|
||||
}
|
||||
},
|
||||
handleShare(report) {
|
||||
this.getProjectApplication();
|
||||
let pram = {};
|
||||
pram.customData = report.id;
|
||||
pram.shareType = 'UI_REPORT';
|
||||
let thisHost = window.location.host;
|
||||
generateShareInfoWithExpired(pram).then((res) => {
|
||||
this.shareUrl = getShareRedirectUrl(res.data);
|
||||
});
|
||||
},
|
||||
getProjectApplication() {
|
||||
let path = "/API_SHARE_REPORT_TIME";
|
||||
if(this.isUi){
|
||||
path = "/UI_SHARE_REPORT_TIME";
|
||||
}
|
||||
this.$get('/project_application/get/' + getCurrentProjectID() + path).then(res => {
|
||||
if (res.data && res.data.typeValue) {
|
||||
let quantity = res.data.typeValue.substring(0, res.data.typeValue.length - 1);
|
||||
let unit = res.data.typeValue.substring(res.data.typeValue.length - 1);
|
||||
if (unit === 'H') {
|
||||
res.data.typeValue = quantity + this.$t('commons.date_unit.hour');
|
||||
} else if (unit === 'D') {
|
||||
res.data.typeValue = quantity + this.$t('commons.date_unit.day');
|
||||
} else if (unit === 'M') {
|
||||
res.data.typeValue = quantity + this.$t('commons.workspace_unit') + this.$t('commons.date_unit.month');
|
||||
} else if (unit === 'Y') {
|
||||
res.data.typeValue = quantity + this.$t('commons.date_unit.year');
|
||||
}
|
||||
this.application = res.data;
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.export-button {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.rerun-button {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
background-color: #F2F9EF;
|
||||
color: #87C45D;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,99 @@
|
|||
<template>
|
||||
<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">
|
||||
{{ $t('api_report.success') }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="danger" v-else>
|
||||
{{ $t('api_report.fail') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-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-dialog :title="$t('api_test.request.assertions.script')" :visible.sync="visible" width="900px" 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>
|
||||
</el-table>
|
||||
</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,481 @@
|
|||
<template>
|
||||
<div class="metric-container">
|
||||
<el-row type="flex" align="middle">
|
||||
<div style="width: 50%">
|
||||
<el-row type="flex" justify="center" align="middle">
|
||||
<el-row>
|
||||
<div class="metric-time">
|
||||
<div class="value" style="margin-right: 50px">{{ time }}</div>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<div v-if="isExport">
|
||||
<span class="ms-req ms-req-error"
|
||||
v-if="(content.error && content.error>0 )|| (content.errorCode && content.errorCode>0)|| (content.unExecute && content.unExecute>0)">
|
||||
<span class="ms-req-span"> {{ content.success + content.error + content.errorCode + content.unExecute }} {{
|
||||
isUi ? '指令' : $t('api_report.request')
|
||||
}}</span>
|
||||
</span>
|
||||
<span class="ms-req ms-req-success" v-else>
|
||||
<span class="ms-req-span"> {{
|
||||
content.success ? content.success + content.error : 0
|
||||
}} {{ isUi ? '指令' : $t('api_report.request') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<ms-chart id="chart" ref="chart" :options="options" :height="220" style="margin-right: 10px"
|
||||
:autoresize="true" v-else/>
|
||||
<el-row type="flex" justify="center" align="middle" style="width: 150px">
|
||||
<div>
|
||||
<div class="metric-icon-box" style="height: 26px">
|
||||
<span class="ms-point-success" style="margin: 7px;float: left;"/>
|
||||
<div class="metric-box">
|
||||
<div class="value" style="font-size: 12px">{{ content.success }} {{ $t('api_report.success') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider></el-divider>
|
||||
<div class="metric-icon-box" style="height: 26px">
|
||||
<span class="ms-point-error" style="margin: 7px;float: left;"/>
|
||||
<div class="metric-box">
|
||||
<div class="value" style="font-size: 12px">{{ content.error }} {{ $t('api_report.fail') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider v-if="content.errorCode > 0"></el-divider>
|
||||
<div class="metric-icon-box" v-if="content.errorCode > 0" style="height: 26px">
|
||||
<span class="ms-point-error-code" style="margin: 7px;float: left;"/>
|
||||
<div class="metric-box" v-if="content.errorCode > 0">
|
||||
<div class="value" style="font-size: 12px">{{ content.errorCode }}
|
||||
{{ $t('error_report_library.option.name') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider v-if="content.unExecute > 0"></el-divider>
|
||||
<div class="metric-icon-box" style="height: 26px" v-if="content.unExecute > 0">
|
||||
<span class="ms-point-unexecute" style="margin: 7px;float: left;"/>
|
||||
<div class="metric-box">
|
||||
<div class="value" style="font-size: 12px">{{ content.unExecute }}
|
||||
{{ $t('api_test.home_page.detail_card.unexecute') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</el-row>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="split"></div>
|
||||
<!-- 场景统计 -->
|
||||
<div style="width: 50%">
|
||||
<el-row type="flex" justify="center" align="middle" v-if="report.reportType !== 'API_INTEGRATED'">
|
||||
<div class="metric-box">
|
||||
<div class="value">{{ content.scenarioTotal ? content.scenarioTotal : 0 }}</div>
|
||||
<div class="name">{{ $t('api_test.scenario.scenario') }}</div>
|
||||
</div>
|
||||
<span class="ms-point-success"/>
|
||||
<div class="metric-box">
|
||||
<div class="value">{{ content.scenarioSuccess ? content.scenarioSuccess : 0 }}</div>
|
||||
<div class="name">{{ $t('api_report.success') }}</div>
|
||||
</div>
|
||||
<span class="ms-point-error"/>
|
||||
<div class="metric-box">
|
||||
<div class="value">{{ content.scenarioError ? content.scenarioError : 0 }}</div>
|
||||
<div class="name">{{ $t('api_report.fail') }}</div>
|
||||
</div>
|
||||
<span class="ms-point-error-code"
|
||||
v-if="content.scenarioErrorReport > 0 || content.scenarioStepErrorReport > 0 "/>
|
||||
<div class="metric-box" v-if="content.scenarioErrorReport > 0 || content.scenarioStepErrorReport > 0 ">
|
||||
<div class="value">{{ content.scenarioErrorReport ? content.scenarioErrorReport : 0 }}</div>
|
||||
<div class="name">{{ $t('error_report_library.option.name') }}</div>
|
||||
</div>
|
||||
<span v-show="showUnExecuteReport" class="ms-point-unexecute"/>
|
||||
<div v-show="showUnExecuteReport" class="metric-box">
|
||||
<div class="value">{{ content.scenarioUnExecute ? content.scenarioUnExecute : 0 }}</div>
|
||||
<div class="name">{{ $t('api_test.home_page.detail_card.unexecute') }}</div>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-divider v-if="report.reportType !== 'API_INTEGRATED'"/>
|
||||
<el-row type="flex" justify="center" align="middle">
|
||||
<el-row type="flex" justify="center" align="middle">
|
||||
<div class="metric-box">
|
||||
<div class="value">{{ content.scenarioStepTotal ? content.scenarioStepTotal : 0 }}</div>
|
||||
<div class="name" v-if="report.reportType === 'API_INTEGRATED'">
|
||||
{{ $t('api_test.definition.request.case') }}
|
||||
</div>
|
||||
<div class="name" v-else>{{ $t('test_track.plan_view.step') }}</div>
|
||||
</div>
|
||||
<span class="ms-point-success"/>
|
||||
<div class="metric-box">
|
||||
<div class="value">{{ content.scenarioStepSuccess ? content.scenarioStepSuccess : 0 }}</div>
|
||||
<div class="name">{{ $t('api_report.success') }}</div>
|
||||
</div>
|
||||
<span class="ms-point-error"/>
|
||||
<div class="metric-box">
|
||||
<div class="value">{{ content.scenarioStepError ? content.scenarioStepError : 0 }}</div>
|
||||
<div class="name">{{ $t('api_report.fail') }}</div>
|
||||
</div>
|
||||
<span class="ms-point-error-code"
|
||||
v-if="content.scenarioErrorReport > 0 || content.scenarioStepErrorReport > 0 "/>
|
||||
<div class="metric-box" v-if="content.scenarioErrorReport > 0 || content.scenarioStepErrorReport > 0 ">
|
||||
<div class="value">{{ content.scenarioStepErrorReport ? content.scenarioStepErrorReport : 0 }}</div>
|
||||
<div class="name">{{ $t('error_report_library.option.name') }}</div>
|
||||
</div>
|
||||
<span v-show="showUnExecuteReport && !isUi" class="ms-point-unexecute"/>
|
||||
<div v-show="showUnExecuteReport && !isUi" class="metric-box">
|
||||
<div class="value">{{
|
||||
content.scenarioStepUnExecuteReport ? content.scenarioStepUnExecuteReport : 0
|
||||
}}
|
||||
</div>
|
||||
<div class="name">{{ $t('api_test.home_page.detail_card.unexecute') }}</div>
|
||||
</div>
|
||||
<span v-show="showUnExecuteReport && isUi" class="ms-point-unexecute"/>
|
||||
<div v-show="showUnExecuteReport && isUi" class="metric-box">
|
||||
<div class="value">{{
|
||||
content.scenarioStepUnExecuteReport ? content.scenarioStepUnExecuteReport : 0
|
||||
}}
|
||||
</div>
|
||||
<div class="name">{{ $t('api_test.home_page.detail_card.unexecute') }}</div>
|
||||
</div>
|
||||
</el-row>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="split"></div>
|
||||
|
||||
<div style="width: 50%">
|
||||
<el-row type="flex" justify="space-around" align="middle">
|
||||
<div class="metric-icon-box">
|
||||
<i class="el-icon-warning-outline fail"></i>
|
||||
<div class="value">{{ fail }}</div>
|
||||
<div class="name">{{ $t('api_report.fail') }}</div>
|
||||
</div>
|
||||
<div class="metric-icon-box">
|
||||
<i class="el-icon-document-checked assertions"></i>
|
||||
<div class="value">{{ assertions }}</div>
|
||||
<div class="name">{{ $t('api_report.assertions_pass') }}</div>
|
||||
</div>
|
||||
<div class="metric-icon-box" v-if="content.errorCode > 0">
|
||||
<i class="el-icon-document-checked assertions"></i>
|
||||
<div class="value">{{ errorCodeAssertions }}</div>
|
||||
<div class="name">{{ $t('error_report_library.assertion') }}</div>
|
||||
</div>
|
||||
<div class="metric-icon-box" v-if="!isUi">
|
||||
<i class="el-icon-document-copy total"></i>
|
||||
<div class="value">{{ this.content.total }}</div>
|
||||
<div class="name">{{ isUi ? '指令' : $t('api_report.request') }}</div>
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsChart from "metersphere-frontend/src/components/chart/MsChart";
|
||||
|
||||
export default {
|
||||
name: "MsMetricChart",
|
||||
components: {MsChart},
|
||||
props: {
|
||||
report: Object,
|
||||
content: Object,
|
||||
totalTime: Number,
|
||||
isExport: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hour: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
time: 0,
|
||||
scenarioTotal: 0,
|
||||
scenarioSuccess: 0,
|
||||
scenarioError: 0,
|
||||
reqTotal: 0,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initTime();
|
||||
},
|
||||
methods: {
|
||||
initTime() {
|
||||
this.time = this.totalTime;
|
||||
this.seconds = (this.time) / 1000;
|
||||
if (this.seconds >= 1) {
|
||||
if (this.seconds < 60) {
|
||||
this.seconds = Math.round(this.seconds * 100 / 1) / 100;
|
||||
this.time = this.seconds + "s"
|
||||
}
|
||||
if (this.seconds > 60) {
|
||||
this.minutes = Math.round(this.seconds / 60)
|
||||
this.seconds = Math.round(this.seconds * 100 % 60) / 100;
|
||||
this.time = this.minutes + "min" + this.seconds + "s"
|
||||
}
|
||||
if (this.minutes > 60) {
|
||||
this.hour = Math.round(this.minutes / 60)
|
||||
this.minutes = Math.round(this.minutes % 60)
|
||||
this.time = this.hour + "hour" + this.minutes + "min" + this.seconds + "s"
|
||||
}
|
||||
|
||||
} else {
|
||||
this.time = this.totalTime + "ms"
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
totalCount() {
|
||||
let total = 0;
|
||||
if (this.content.success) {
|
||||
total += this.content.success;
|
||||
}
|
||||
if (this.content.error) {
|
||||
total += this.content.error;
|
||||
}
|
||||
if (this.content.errorCode) {
|
||||
total += this.content.errorCode;
|
||||
}
|
||||
if (this.content.unExecute) {
|
||||
total += this.content.unExecute;
|
||||
}
|
||||
return total;
|
||||
},
|
||||
options() {
|
||||
return {
|
||||
color: ['#67C23A', '#F56C6C', '#F6972A', '#9C9B9A'],
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c} ({d}%)'
|
||||
},
|
||||
title: [{
|
||||
text: this.totalCount,
|
||||
subtext: this.isUi ? '指令' : this.$t('api_report.request'),
|
||||
top: 'center',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
rich: {
|
||||
align: 'center',
|
||||
value: {
|
||||
fontSize: 32,
|
||||
fontWeight: 'bold',
|
||||
padding: [10, 0]
|
||||
},
|
||||
name: {
|
||||
fontSize: 14,
|
||||
fontWeight: 'normal',
|
||||
color: '#7F7F7F',
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['80%', '90%'],
|
||||
avoidLabelOverlap: false,
|
||||
hoverAnimation: false,
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
itemStyle: {
|
||||
borderColor: "#FFF",
|
||||
shadowColor: '#E1E1E1',
|
||||
shadowBlur: 10
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
{value: this.content.success, name: this.$t('api_report.success')},
|
||||
{value: this.content.error, name: this.$t('api_report.fail')},
|
||||
{value: this.content.errorCode, name: this.$t('error_report_library.option.name')},
|
||||
{value: this.content.unExecute, name: this.$t('api_test.home_page.detail_card.unexecute')},
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
fail() {
|
||||
return (this.content.error / this.content.total * 100).toFixed(0) + "%";
|
||||
},
|
||||
assertions() {
|
||||
return this.content.passAssertions + " / " + this.content.totalAssertions;
|
||||
},
|
||||
errorCodeAssertions() {
|
||||
return this.content.errorCode + " / " + this.content.totalAssertions;
|
||||
},
|
||||
isUi() {
|
||||
return this.report.reportType && this.report.reportType.startsWith("UI");
|
||||
},
|
||||
showUnExecuteReport() {
|
||||
return (this.content.scenarioStepUnExecuteReport && this.content.scenarioStepUnExecuteReport > 0)
|
||||
|| (this.content.scenarioUnExecute && this.content.scenarioUnExecute > 0) || (this.content.unExecute && this.content.unExecute > 0);
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.metric-container {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.metric-container #chart {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.metric-container .split {
|
||||
margin: 20px;
|
||||
height: 100px;
|
||||
border-left: 1px solid #D8DBE1;
|
||||
}
|
||||
|
||||
.metric-container .circle {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 20px 1px rgba(200, 216, 226, .42);
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.metric-container .circle.success {
|
||||
background-color: #67C23A;
|
||||
}
|
||||
|
||||
.metric-container .circle.fail {
|
||||
background-color: #F56C6C;
|
||||
}
|
||||
|
||||
.metric-box {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
min-width: 62px;
|
||||
}
|
||||
|
||||
.metric-box .value {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -.5px;
|
||||
}
|
||||
|
||||
.metric-time .value {
|
||||
font-size: 25px;
|
||||
font-weight: 400;
|
||||
letter-spacing: -.5px;
|
||||
}
|
||||
|
||||
.metric-box .name {
|
||||
font-size: 16px;
|
||||
letter-spacing: -.2px;
|
||||
color: #404040;
|
||||
}
|
||||
|
||||
.metric-icon-box {
|
||||
text-align: center;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.metric-icon-box .value {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -.4px;
|
||||
line-height: 28px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.metric-icon-box .name {
|
||||
font-size: 13px;
|
||||
letter-spacing: 1px;
|
||||
color: #BFBFBF;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.metric-icon-box .fail {
|
||||
color: #F56C6C;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.metric-icon-box .assertions {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.metric-icon-box .total {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.ms-req {
|
||||
border-radius: 50%;
|
||||
height: 110px;
|
||||
width: 110px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.ms-req-error {
|
||||
border: 5px #F56C6C solid;
|
||||
}
|
||||
|
||||
.ms-req-success {
|
||||
border: 5px #67C23A solid;
|
||||
}
|
||||
|
||||
.ms-req-span {
|
||||
display: block;
|
||||
color: black;
|
||||
height: 110px;
|
||||
line-height: 110px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ms-point-success {
|
||||
border-radius: 50%;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
min-width: 12px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
background-color: #67C23A;
|
||||
}
|
||||
|
||||
.ms-point-error {
|
||||
border-radius: 50%;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
min-width: 12px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
background-color: #F56C6C;
|
||||
}
|
||||
|
||||
.ms-point-error-code {
|
||||
border-radius: 50%;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
min-width: 12px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
background-color: #F6972A;
|
||||
}
|
||||
|
||||
.ms-point-unexecute {
|
||||
border-radius: 50%;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
min-width: 12px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
background-color: #9C9B9A;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,435 @@
|
|||
<template>
|
||||
<el-card class="ms-cards" v-if="request && request.responseResult">
|
||||
<div class="request-result">
|
||||
<div @click="active">
|
||||
<el-row :gutter="18" type="flex" align="middle" class="info">
|
||||
<el-col class="ms-req-name-col" :span="18" v-if="indexNumber!=undefined">
|
||||
<el-tooltip :content="getName(request.name)" placement="top">
|
||||
<div class="method ms-req-name">
|
||||
<div class="el-step__icon is-text ms-api-col-create">
|
||||
<div class="el-step__icon-inner"> {{ indexNumber }}</div>
|
||||
</div>
|
||||
<i class="icon el-icon-arrow-right" :class="{'is-active': showActive}" @click="active" @click.stop/>
|
||||
<el-link class="report-label-req" @click="isLink" v-if="redirect && resourceId">
|
||||
{{ request.name }}
|
||||
</el-link>
|
||||
<span v-else>{{ getName(request.name) }}</span>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<div v-if="totalStatus">
|
||||
<el-tooltip effect="dark" v-if="baseErrorCode && baseErrorCode!==''" :content="baseErrorCode"
|
||||
style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" placement="bottom"
|
||||
:open-delay="800">
|
||||
<div v-if="totalStatus === 'Success'|| totalStatus === 'success'" style="color: #5daf34">
|
||||
{{ baseErrorCode }}
|
||||
</div>
|
||||
<div v-else-if="totalStatus === 'errorReportResult'" style="color: #F6972A">
|
||||
{{ baseErrorCode }}
|
||||
</div>
|
||||
<div v-else style="color: #FE6F71">
|
||||
{{ baseErrorCode }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-tooltip effect="dark" v-if="baseErrorCode && baseErrorCode!==''" :content="baseErrorCode"
|
||||
style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" placement="bottom"
|
||||
:open-delay="800">
|
||||
<div v-if="request.success" style="color: #F6972A">
|
||||
{{ baseErrorCode }}
|
||||
</div>
|
||||
<div v-else style="color: #FE6F71">
|
||||
{{ baseErrorCode }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div v-if="totalStatus">
|
||||
<el-tooltip effect="dark" :content="request.responseResult.responseCode"
|
||||
style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" placement="bottom"
|
||||
:open-delay="800">
|
||||
<div v-if="totalStatus === 'Success'|| totalStatus === 'success'" style="color: #5daf34">
|
||||
{{ request.responseResult.responseCode }}
|
||||
</div>
|
||||
<div v-else-if="totalStatus === 'errorReportResult'" style="color: #F6972A">
|
||||
{{ request.responseResult.responseCode }}
|
||||
</div>
|
||||
<div style="color: #FE6F71" v-else>
|
||||
{{ request.responseResult.responseCode }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-tooltip effect="dark" :content="request.responseResult.responseCode"
|
||||
style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" placement="bottom"
|
||||
:open-delay="800">
|
||||
<div style="color: #F6972A" v-if="baseErrorCode && baseErrorCode!=='' && request.success">
|
||||
{{ request.responseResult.responseCode }}
|
||||
</div>
|
||||
<div style="color: #5daf34" v-else-if="request.success">
|
||||
{{ request.responseResult.responseCode }}
|
||||
</div>
|
||||
<div style="color: #FE6F71" v-else>
|
||||
{{ request.responseResult.responseCode }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<div v-if="totalStatus">
|
||||
<div v-if="totalStatus === 'Success'|| totalStatus === 'success'" style="color: #5daf34">
|
||||
{{ request.responseResult.responseTime }}
|
||||
</div>
|
||||
<div v-else-if="totalStatus === 'errorReportResult'" style="color: #F6972A">
|
||||
{{ request.responseResult.responseTime }}
|
||||
</div>
|
||||
<div style="color: #FE6F71" v-else>
|
||||
{{ request.responseResult.responseTime }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span v-if="request.success">
|
||||
{{ request.responseResult.responseTime }} ms
|
||||
</span>
|
||||
<span style="color: #FE6F71" v-else>
|
||||
{{ request.responseResult.responseTime }} ms
|
||||
</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<div v-if="totalStatus">
|
||||
<el-tag size="mini" v-if="totalStatus === 'unexecute'">{{
|
||||
$t('api_test.home_page.detail_card.unexecute')
|
||||
}}
|
||||
</el-tag>
|
||||
<el-tag v-else-if="totalStatus === 'errorReportResult' " class="ms-test-error_code"
|
||||
size="mini">
|
||||
{{ $t('error_report_library.option.name') }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="success" v-else-if="totalStatus === 'Success' || totalStatus === 'success'">
|
||||
{{ $t('api_report.success') }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="danger" v-else> {{ $t('api_report.fail') }}</el-tag>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-tag v-if="request.testing" class="ms-test-running" size="mini">
|
||||
<i class="el-icon-loading" style="font-size: 16px"/>
|
||||
{{ $t('commons.testing') }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" v-else-if="request.unexecute">{{
|
||||
$t('api_test.home_page.detail_card.unexecute')
|
||||
}}
|
||||
</el-tag>
|
||||
<el-tag size="mini" v-else-if="!request.success && request.status && request.status==='unexecute'">{{
|
||||
$t('api_test.home_page.detail_card.unexecute')
|
||||
}}
|
||||
</el-tag>
|
||||
<el-tag v-else-if="baseErrorCode && baseErrorCode!== '' && request.success" class="ms-test-error_code"
|
||||
size="mini">
|
||||
{{ $t('error_report_library.option.name') }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="success" v-else-if="request.success"> {{ $t('api_report.success') }}</el-tag>
|
||||
<el-tag size="mini" type="danger" v-else> {{ $t('api_report.fail') }}</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<el-collapse-transition>
|
||||
<div v-show="showActive && !request.unexecute" style="width: 99%">
|
||||
<ms-request-result-tail
|
||||
v-loading="requestInfo.loading"
|
||||
:scenario-name="scenarioName"
|
||||
:request-type="requestType"
|
||||
:request="requestInfo"
|
||||
:console="console"
|
||||
v-if="showActive"/>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsRequestResultTail from "./RequestResultTail";
|
||||
|
||||
export default {
|
||||
name: "MsRequestResult",
|
||||
components: {
|
||||
MsRequestResultTail
|
||||
},
|
||||
props: {
|
||||
request: Object,
|
||||
resourceId: String,
|
||||
scenarioName: String,
|
||||
stepId: String,
|
||||
indexNumber: Number,
|
||||
console: String,
|
||||
totalStatus: String,
|
||||
redirect: Boolean,
|
||||
errorCode: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isShare: Boolean,
|
||||
shareId: String,
|
||||
},
|
||||
created() {
|
||||
this.showActive = this.isActive;
|
||||
this.baseErrorCode = this.errorCode;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
requestType: "",
|
||||
color: {
|
||||
type: String,
|
||||
default() {
|
||||
return "#B8741A";
|
||||
}
|
||||
},
|
||||
requestInfo: {
|
||||
loading: true,
|
||||
hasData: false,
|
||||
responseResult: {},
|
||||
subRequestResults: [],
|
||||
},
|
||||
baseErrorCode: "",
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default() {
|
||||
return "#F9F1EA";
|
||||
}
|
||||
},
|
||||
showActive: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isActive() {
|
||||
this.loadRequestInfoExpand();
|
||||
this.showActive = this.isActive;
|
||||
},
|
||||
errorCode() {
|
||||
this.baseErrorCode = this.errorCode;
|
||||
},
|
||||
request: {
|
||||
deep: true,
|
||||
handler(n) {
|
||||
if (this.request.errorCode) {
|
||||
this.baseErrorCode = this.request.errorCode;
|
||||
} else if (this.request.attachInfoMap && this.request.attachInfoMap.errorReportResult) {
|
||||
if (this.request.attachInfoMap.errorReportResult !== "") {
|
||||
this.baseErrorCode = this.request.attachInfoMap.errorReportResult;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isLink() {
|
||||
let uri = "/#/api/definition?caseId=" + this.resourceId;
|
||||
this.clickResource(uri)
|
||||
},
|
||||
clickResource(uri) {
|
||||
this.$get('/user/update/currentByResourceId/' + this.resourceId).then(() => {
|
||||
this.toPage(uri);
|
||||
});
|
||||
},
|
||||
toPage(uri) {
|
||||
let id = "new_a";
|
||||
let a = document.createElement("a");
|
||||
a.setAttribute("href", uri);
|
||||
a.setAttribute("target", "_blank");
|
||||
a.setAttribute("id", id);
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
let element = document.getElementById(id);
|
||||
element.parentNode.removeChild(element);
|
||||
},
|
||||
loadRequestInfoExpand() {
|
||||
if (!this.request.responseResult || this.request.responseResult.body === null || this.request.responseResult.body === undefined) {
|
||||
if (this.isShare) {
|
||||
this.$get("/share/" + this.shareId + "/scenario/report/selectReportContent/" + this.stepId).then(response => {
|
||||
this.requestInfo = response.data;
|
||||
this.$nextTick(() => {
|
||||
this.requestInfo.loading = false;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.$get("/ui/scenario/report/selectReportContent/" + this.stepId).then(response => {
|
||||
this.requestInfo = response.data;
|
||||
this.$nextTick(() => {
|
||||
this.requestInfo.loading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
this.requestInfo = this.request;
|
||||
}
|
||||
},
|
||||
active() {
|
||||
if (this.request.unexecute) {
|
||||
this.showActive = false;
|
||||
} else {
|
||||
this.showActive = !this.showActive;
|
||||
}
|
||||
if (this.showActive) {
|
||||
this.loadRequestInfoExpand();
|
||||
}
|
||||
},
|
||||
getName(name) {
|
||||
if (name && name.indexOf("<->") !== -1) {
|
||||
return name.split("<->")[0];
|
||||
}
|
||||
if (name && name.indexOf("^@~@^") !== -1) {
|
||||
let arr = name.split("^@~@^");
|
||||
let value = arr[arr.length - 1];
|
||||
if (value.indexOf("UUID=") !== -1) {
|
||||
return value.split("UUID=")[0];
|
||||
}
|
||||
if (value && value.startsWith("UUID=")) {
|
||||
return "";
|
||||
}
|
||||
if (value && value.indexOf("<->") !== -1) {
|
||||
return value.split("<->")[0];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
if (name && name.startsWith("UUID=")) {
|
||||
return "";
|
||||
}
|
||||
return name;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-result {
|
||||
min-height: 30px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.request-result .info {
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.request-result .method {
|
||||
color: #1E90FF;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 35px;
|
||||
padding-left: 5px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.ms-cards :deep(.el-card__body) {
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.sub-result:last-child {
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.ms-test-running {
|
||||
color: #783887;
|
||||
}
|
||||
|
||||
.ms-test-error_code {
|
||||
color: #F6972A;
|
||||
background-color: #FDF5EA;
|
||||
border-color: #FDF5EA;
|
||||
}
|
||||
|
||||
.ms-api-col {
|
||||
background-color: #EFF0F0;
|
||||
border-color: #EFF0F0;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
color: #64666A;
|
||||
}
|
||||
|
||||
.ms-api-col-create {
|
||||
background-color: #EBF2F2;
|
||||
border-color: #008080;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
color: #008080;
|
||||
}
|
||||
|
||||
:deep(.el-step__icon) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.el-divider--horizontal {
|
||||
margin: 2px 0;
|
||||
background: 0 0;
|
||||
border-top: 1px solid #e8eaec;
|
||||
}
|
||||
|
||||
.icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.ms-req-name {
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
padding-bottom: 0;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.ms-req-name-col {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.report-label-req {
|
||||
height: 20px;
|
||||
border-bottom: 1px solid #303133;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,146 @@
|
|||
<template>
|
||||
<div class="request-result">
|
||||
<div>
|
||||
<el-row :gutter="8" type="flex" align="middle" class="info">
|
||||
<el-col :span="2">
|
||||
<div class="method">
|
||||
{{ request.method }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-tooltip effect="dark" :content="request.url" placement="bottom" :open-delay="800">
|
||||
<div class="url">{{ request.url }}</div>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="url">
|
||||
{{ $t('api_report.start_time') }}:{{ request.startTime | timestampFormatDate(true) }}
|
||||
{{ $t('report.test_end_time') }}:{{ request.endTime | timestampFormatDate(true) }}
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<div v-show="isActive">
|
||||
<el-tabs v-model="activeName" v-show="isActive" v-if="hasSub">
|
||||
<el-tab-pane :label="$t('api_report.sub_result')" name="sub">
|
||||
<ms-request-sub-result class="sub-result" v-for="(sub, index) in request.subRequestResults"
|
||||
:key="index" :indexNumber="index" :request="sub"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_report.request_result')" name="result">
|
||||
<ms-response-text :console="console" :request-type="requestType" :response="request.responseResult" :request="request"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div v-else>
|
||||
<ms-response-text :console="console" :request-type="requestType" v-if="isCodeEditAlive" :response="request.responseResult" :request="request"/>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsResponseText from "./ResponseText";
|
||||
import MsRequestSubResult from "./RequestSubResult";
|
||||
|
||||
export default {
|
||||
name: "MsRequestResultTail",
|
||||
components: {MsResponseText, MsRequestSubResult},
|
||||
props: {
|
||||
request: Object,
|
||||
scenarioName: String,
|
||||
requestType: String,
|
||||
console: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: true,
|
||||
activeName: "sub",
|
||||
isCodeEditAlive: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
reload() {
|
||||
this.isCodeEditAlive = false;
|
||||
this.$nextTick(() => (this.isCodeEditAlive = true));
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
'request.responseResult'() {
|
||||
this.reload();
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasSub() {
|
||||
return this.request.subRequestResults.length > 0;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-result {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.request-result .info {
|
||||
background-color: #F9F9F9;
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.request-result .method {
|
||||
color: #1E90FF;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.request-result .url {
|
||||
color: #7f7f7f;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-top: 4px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.request-result .tab .el-tabs__header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.request-result .text {
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sub-result .info {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.sub-result .method {
|
||||
border-left: 5px solid #1E90FF;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.sub-result:last-child {
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.request-result .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,191 @@
|
|||
<template>
|
||||
<div class="request-result">
|
||||
<p class="el-divider--horizontal"></p>
|
||||
<div @click="active">
|
||||
<el-row :gutter="10" type="flex" align="middle" class="info">
|
||||
<el-col :span="6" v-if="indexNumber!=undefined">
|
||||
<div class="method">
|
||||
|
||||
<div style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
|
||||
<div class="el-step__icon is-text ms-api-col" v-if="indexNumber%2 ==0">
|
||||
<div class="el-step__icon-inner"> {{ indexNumber + 1 }}</div>
|
||||
</div>
|
||||
<div class="el-step__icon is-text ms-api-col-create" v-else>
|
||||
<div class="el-step__icon-inner"> {{ indexNumber + 1 }}</div>
|
||||
</div>
|
||||
<i class="icon el-icon-arrow-right" :class="{'is-active': isActive}" @click="active" @click.stop/>
|
||||
{{ getName(request.name) }}
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<div>
|
||||
{{ request.method }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="url">
|
||||
<el-tooltip :content="request.url " style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" placement="bottom" :open-delay="800">
|
||||
<div>
|
||||
{{ request.url }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-tooltip effect="dark" :content="request.responseResult.responseCode" placement="bottom" :open-delay="800">
|
||||
<div class="url" style="color: #5daf34">{{ request.responseResult.responseCode }}</div>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
{{ request.responseResult.responseTime }} ms
|
||||
</el-col>
|
||||
|
||||
<el-col :span="2">
|
||||
<div class="success">
|
||||
<el-tag size="mini" type="success" v-if="request.success">
|
||||
{{ $t('api_report.success') }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="danger" v-else>
|
||||
{{ $t('api_report.fail') }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<div v-show="isActive" style="width: 99%">
|
||||
<ms-request-sub-result-tail :scenario-name="scenarioName"
|
||||
:request-type="requestType" v-if="isActive"
|
||||
:request="request"/>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsRequestSubResultTail from "./RequestSubResultTail";
|
||||
|
||||
export default {
|
||||
name: "MsRequestSubResult",
|
||||
components: {
|
||||
MsRequestSubResultTail
|
||||
},
|
||||
props: {
|
||||
request: Object,
|
||||
scenarioName: String,
|
||||
indexNumber: Number,
|
||||
},
|
||||
data() {
|
||||
return {isActive: false, requestType: undefined,}
|
||||
},
|
||||
methods: {
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
getName(name) {
|
||||
if (name && name.indexOf("<->") !== -1) {
|
||||
return name.split("<->")[0];
|
||||
}
|
||||
if (name && name.indexOf("^@~@^") !== -1) {
|
||||
let arr = name.split("^@~@^");
|
||||
let value = arr[arr.length - 1];
|
||||
if (value.indexOf("UUID=") !== -1) {
|
||||
return value.split("UUID=")[0];
|
||||
}
|
||||
if (value && value.startsWith("UUID=")) {
|
||||
return "";
|
||||
}
|
||||
if (value && value.indexOf("<->") !== -1) {
|
||||
return value.split("<->")[0];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
if (name && name.startsWith("UUID=")) {
|
||||
return "";
|
||||
}
|
||||
return name;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-result {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.request-result .info {
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.request-result .method {
|
||||
color: #1E90FF;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.request-result .url {
|
||||
color: #7f7f7f;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-top: 4px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.request-result .tab .el-tabs__header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.request-result .text {
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sub-result .info {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.sub-result .method {
|
||||
border-left: 5px solid #1E90FF;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.sub-result:last-child {
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.ms-api-col {
|
||||
background-color: #EFF0F0;
|
||||
border-color: #EFF0F0;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
color: #64666A;
|
||||
}
|
||||
|
||||
.ms-api-col-create {
|
||||
background-color: #EBF2F2;
|
||||
border-color: #008080;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
color: #008080;
|
||||
}
|
||||
|
||||
.el-divider--horizontal {
|
||||
margin: 2px 0;
|
||||
background: 0 0;
|
||||
border-top: 1px solid #e8eaec;
|
||||
}
|
||||
|
||||
.icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,133 @@
|
|||
<template>
|
||||
<div class="request-result">
|
||||
<div>
|
||||
<el-row :gutter="8" type="flex" align="middle" class="info">
|
||||
<el-col :span="2">
|
||||
<div class="method">
|
||||
{{request.method}}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-tooltip effect="dark" :content="request.url" placement="bottom" :open-delay="800">
|
||||
<div class="url">{{request.url}}</div>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="url"> {{$t('api_report.start_time')}}:{{request.startTime | timestampFormatDate(true) }}
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<div v-show="isActive">
|
||||
<ms-response-text :request-type="requestType" v-if="isCodeEditAlive" :response="request.responseResult" :request="request"/>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsResponseText from "./ResponseText";
|
||||
|
||||
export default {
|
||||
name: "MsRequestSubResultTail",
|
||||
components: {MsResponseText},
|
||||
props: {
|
||||
request: Object,
|
||||
scenarioName: String,
|
||||
requestType: String
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: true,
|
||||
activeName: "sub",
|
||||
isCodeEditAlive: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
reload() {
|
||||
this.isCodeEditAlive = false;
|
||||
this.$nextTick(() => (this.isCodeEditAlive = true));
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'request.responseResult'() {
|
||||
this.reload();
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
assertion() {
|
||||
return this.request.passAssertions + " / " + this.request.totalAssertions;
|
||||
},
|
||||
hasSub() {
|
||||
return this.request.subRequestResults.length > 0;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-result {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.request-result .info {
|
||||
background-color: #F9F9F9;
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.request-result .method {
|
||||
color: #1E90FF;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.request-result .url {
|
||||
color: #7f7f7f;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-top: 4px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.request-result .tab .el-tabs__header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.request-result .text {
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sub-result .info {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.sub-result .method {
|
||||
border-left: 5px solid #1E90FF;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.sub-result:last-child {
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.request-result .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,144 @@
|
|||
<template>
|
||||
<div class="text-container">
|
||||
<el-collapse-transition>
|
||||
<el-tabs v-model="activeName" v-show="isActive">
|
||||
<el-tab-pane :class="'body-pane'" :label="$t('api_test.definition.request.response_body')" name="body" class="pane">
|
||||
<ms-code-edit :mode="mode" :read-only="true" :data="response.body" :modes="modes" ref="codeEdit"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_test.definition.request.response_header')" name="headers" class="pane">
|
||||
<pre>{{ response.headers }}</pre>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_report.assertions')" name="assertions" class="pane assertions">
|
||||
<ms-assertion-results :assertions="response.assertions"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_test.request.extract.label')" name="label" class="pane">
|
||||
<pre>{{ response.vars }}</pre>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_report.request_body')" name="request_body" class="pane">
|
||||
<div class="ms-div">
|
||||
{{ $t('api_test.request.address') }} :
|
||||
<pre>{{ request.url }}</pre>
|
||||
</div>
|
||||
<div class="ms-div">
|
||||
{{ $t('api_test.scenario.headers') }} :
|
||||
<pre>{{ request.headers }}</pre>
|
||||
</div>
|
||||
<div class="ms-div">
|
||||
Cookies :
|
||||
<pre>{{ request.cookies }}</pre>
|
||||
</div>
|
||||
<div class="ms-div">
|
||||
Body :
|
||||
<pre>{{ request.body }}</pre>
|
||||
</div>
|
||||
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane v-if="activeName == 'body'" :disabled="true" name="mode" class="pane assertions">
|
||||
<template v-slot:label>
|
||||
<ms-dropdown v-if="request.method==='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>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsAssertionResults from "./AssertionResults";
|
||||
import MsCodeEdit from "metersphere-frontend/src/components/MsCodeEdit";
|
||||
import MsDropdown from "metersphere-frontend/src/components/MsDropdown";
|
||||
|
||||
export default {
|
||||
name: "MsResponseText",
|
||||
|
||||
components: {
|
||||
MsDropdown,
|
||||
MsCodeEdit,
|
||||
MsAssertionResults,
|
||||
},
|
||||
|
||||
props: {
|
||||
requestType: String,
|
||||
request: {},
|
||||
response: Object,
|
||||
console: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: true,
|
||||
activeName: "body",
|
||||
modes: ['text', 'json', 'xml', 'html'],
|
||||
sqlModes: ['text', 'table'],
|
||||
mode: BODY_FORMAT.TEXT
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
modeChange(mode) {
|
||||
this.mode = mode;
|
||||
},
|
||||
sqlModeChange(mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (!this.response.headers) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.body-pane {
|
||||
padding: 10px !important;
|
||||
background: white !important;
|
||||
}
|
||||
|
||||
.text-container .icon {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.text-container .collapse {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.text-container .collapse:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.text-container .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.text-container .pane {
|
||||
background-color: #F5F5F5;
|
||||
padding: 1px 0;
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.text-container .pane.assertions {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ms-div {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,184 @@
|
|||
<template>
|
||||
<div class="scenario-result">
|
||||
<div v-if="(node.children && node.children.length >0) || node.unsolicited
|
||||
|| (node.type && this.stepFilter.get('AllSamplerProxy').indexOf(node.type) === -1)">
|
||||
<el-card class="ms-card">
|
||||
<div class="el-step__icon is-text ms-api-col">
|
||||
<div class="el-step__icon-inner">
|
||||
{{ node.index }}
|
||||
</div>
|
||||
</div>
|
||||
<el-tooltip effect="dark" :content="node.label" placement="top">
|
||||
<el-link v-if="node.redirect" class="report-label-head" @click="isLink">
|
||||
{{ getLabel(node.label) }}
|
||||
</el-link>
|
||||
<span v-else>{{ getLabel(node.label) }}</span>
|
||||
</el-tooltip>
|
||||
</el-card>
|
||||
</div>
|
||||
<div v-else-if="node.type === 'MsUiCommand'">
|
||||
<ui-command-result
|
||||
:step-id="node.stepId"
|
||||
:index-number="node.index"
|
||||
:tree-node="treeNode"
|
||||
:command="node"
|
||||
:isActive="isActive"
|
||||
:result="node.value"/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<ms-request-result
|
||||
:step-id="node.stepId"
|
||||
:request="node.value"
|
||||
:redirect="node.redirect"
|
||||
:indexNumber="node.index"
|
||||
:error-code="node.errorCode"
|
||||
:scenarioName="node.label"
|
||||
:resourceId="node.resourceId"
|
||||
:total-status="node.totalStatus"
|
||||
:console="console"
|
||||
:isActive="isActive"
|
||||
:is-share="isShare"
|
||||
:share-id="shareId"
|
||||
v-on:requestResult="requestResult"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsRequestResult from "./RequestResult";
|
||||
import {STEP} from "./Setting";
|
||||
import UiCommandResult from "./UiCommandResult";
|
||||
|
||||
export default {
|
||||
name: "MsScenarioResult",
|
||||
components: {
|
||||
UiCommandResult,
|
||||
MsRequestResult
|
||||
},
|
||||
props: {
|
||||
scenario: Object,
|
||||
node: Object,
|
||||
treeNode: Object,
|
||||
console: String,
|
||||
isActive: Boolean,
|
||||
isShare: Boolean,
|
||||
shareId: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
stepFilter: new STEP,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getLabel(label) {
|
||||
switch (label) {
|
||||
case "ConstantTimer":
|
||||
return "等待控制器";
|
||||
case "LoopController":
|
||||
return "循环控制器";
|
||||
case "Assertion":
|
||||
return "场景断言";
|
||||
default:
|
||||
return label;
|
||||
}
|
||||
},
|
||||
isLink() {
|
||||
let uri = "/#/api/automation?resourceId=" + this.node.resourceId;
|
||||
this.clickResource(uri)
|
||||
},
|
||||
clickResource(uri) {
|
||||
this.$get('/user/update/currentByResourceId/' + this.node.resourceId).then(() => {
|
||||
this.toPage(uri);
|
||||
});
|
||||
},
|
||||
toPage(uri) {
|
||||
let id = "new_a";
|
||||
let a = document.createElement("a");
|
||||
a.setAttribute("href", uri);
|
||||
a.setAttribute("target", "_blank");
|
||||
a.setAttribute("id", id);
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
let element = document.getElementById(id);
|
||||
element.parentNode.removeChild(element);
|
||||
},
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
requestResult(requestResult) {
|
||||
this.$emit("requestResult", requestResult);
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
assertion() {
|
||||
return this.scenario.passAssertions + " / " + this.scenario.totalAssertions;
|
||||
},
|
||||
success() {
|
||||
return this.scenario.error === 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scenario-result {
|
||||
width: 100%;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.scenario-result + .scenario-result {
|
||||
border-top: 1px solid #DCDFE6;
|
||||
}
|
||||
|
||||
.ms-card :deep(.el-card__body) {
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.scenario-result .info {
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scenario-result .icon {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.scenario-result .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.ms-api-col {
|
||||
background-color: #EFF0F0;
|
||||
border-color: #EFF0F0;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
color: #64666A;
|
||||
}
|
||||
|
||||
.ms-card .ms-api-col-create {
|
||||
background-color: #EBF2F2;
|
||||
border-color: #008080;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
color: #008080;
|
||||
}
|
||||
|
||||
.report-label-head {
|
||||
border-bottom: 1px solid #303133;
|
||||
color: #303133;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-step__icon) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,179 @@
|
|||
<template>
|
||||
<el-card class="scenario-results">
|
||||
<div v-if="errorReport > 0">
|
||||
<el-tooltip :content="$t('api_test.automation.open_expansion')" placement="top" effect="light">
|
||||
<i class="el-icon-circle-plus-outline ms-open-btn ms-open-btn-left" v-prevent-re-click @click="openExpansion"/>
|
||||
</el-tooltip>
|
||||
<el-tooltip :content="$t('api_test.automation.close_expansion')" placement="top" effect="light">
|
||||
<i class="el-icon-remove-outline ms-open-btn" size="mini" @click="closeExpansion"/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-tree :data="treeData"
|
||||
:expand-on-click-node="false"
|
||||
:default-expand-all="defaultExpand"
|
||||
:filter-node-method="filterNode"
|
||||
highlight-current
|
||||
class="ms-tree ms-report-tree" ref="resultsTree">
|
||||
<span slot-scope="{ node, data}" style="width: 99%" @click="nodeClick(node)">
|
||||
<ms-scenario-result :node="data" :tree-node="node" :console="console" v-on:requestResult="requestResult"
|
||||
:isActive="isActive" :is-share="isShare" :share-id="shareId"/>
|
||||
</span>
|
||||
</el-tree>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsScenarioResult from "./ScenarioResult";
|
||||
|
||||
export default {
|
||||
name: "MsScenarioResults",
|
||||
components: {MsScenarioResult},
|
||||
props: {
|
||||
scenarios: Array,
|
||||
treeData: Array,
|
||||
console: String,
|
||||
errorReport: Number,
|
||||
report: Object,
|
||||
defaultExpand: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
isShare: Boolean,
|
||||
shareId: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isActive: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.$refs.resultsTree && this.$refs.resultsTree.root) {
|
||||
this.$refs.resultsTree.root.expanded = true;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isUi() {
|
||||
return this.report && this.report.reportType && this.report.reportType.startsWith("UI");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
filterNode(value, data) {
|
||||
if (!data.value && (!data.children || data.children.length === 0)) {
|
||||
return false;
|
||||
}
|
||||
if (!value) return true;
|
||||
if (data.value) {
|
||||
if (value === 'errorReport') {
|
||||
if (data.errorCode && data.errorCode !== "" && data.value.status === "errorReportResult") {
|
||||
return true;
|
||||
}
|
||||
} else if (value === 'unexecute') {
|
||||
if (data.value.status === 'unexecute') {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (this.isUi) {
|
||||
return data.value.success === false && data.value.startTime > 0;
|
||||
} else {
|
||||
return data.totalStatus !== 'errorReportResult' && data.value.error > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
filter(val) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.resultsTree.filter(val);
|
||||
});
|
||||
},
|
||||
requestResult(requestResult) {
|
||||
this.$emit("requestResult", requestResult);
|
||||
},
|
||||
nodeClick(node) {
|
||||
node.expanded = !node.expanded;
|
||||
},
|
||||
// 改变节点的状态
|
||||
changeTreeNodeStatus(node, expandCount) {
|
||||
node.expanded = this.expandAll
|
||||
for (let i = 0; i < node.childNodes.length; i++) {
|
||||
// 改变节点的自身expanded状态
|
||||
node.childNodes[i].expanded = this.expandAll
|
||||
// 遍历子节点
|
||||
if (node.childNodes[i].childNodes.length > 0) {
|
||||
this.changeTreeNodeStatus(node.childNodes[i])
|
||||
}
|
||||
}
|
||||
},
|
||||
closeExpansion() {
|
||||
this.isActive = false;
|
||||
this.expandAll = false;
|
||||
this.changeTreeNodeStatus(this.$refs.resultsTree.store.root, 0);
|
||||
},
|
||||
openExpansion() {
|
||||
this.isActive = true;
|
||||
this.expandAll = true;
|
||||
// 改变每个节点的状态
|
||||
this.changeTreeNodeStatus(this.$refs.resultsTree.store.root, 0)
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scenario-results {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ms-report-tree :deep(.el-tree-node__content) {
|
||||
height: 100%;
|
||||
vertical-align: center;
|
||||
}
|
||||
|
||||
:deep(.el-drawer__body) {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:deep(.el-step__icon.is-text) {
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
:deep(.el-drawer__header) {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
:deep(.el-link) {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
:deep(.el-checkbox) {
|
||||
color: #303133;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
:deep(.el-checkbox__label) {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.ms-sc-variable-header :deep(.el-dialog__body) {
|
||||
padding: 0px 20px;
|
||||
}
|
||||
|
||||
.ms-open-btn {
|
||||
margin: 5px 5px 0px;
|
||||
color: #783887;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.ms-open-btn:hover {
|
||||
background-color: #F2F9EE;
|
||||
cursor: pointer;
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
.ms-open-btn-left {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,120 @@
|
|||
export function STEP() {
|
||||
let map = new Map([
|
||||
['ALL', init()],
|
||||
['scenario', init()],
|
||||
['HTTPSamplerProxy', getDefaultSamplerMenu()],
|
||||
['DubboSampler', getDefaultSamplerMenu()],
|
||||
['JDBCSampler', getDefaultSamplerMenu()],
|
||||
['TCPSampler', getDefaultSamplerMenu()],
|
||||
['OT_IMPORT', getDefaultSamplerMenu()],
|
||||
['AbstractSampler', getDefaultSamplerMenu()],
|
||||
['IfController', getAll()],
|
||||
['TransactionController', getAll()],
|
||||
['LoopController', getAll()],
|
||||
['ConstantTimer', []],
|
||||
['JSR223Processor', getDefaultSamplerMenu()],
|
||||
['JSR223PreProcessor', []],
|
||||
['JSR223PostProcessor', []],
|
||||
['JDBCPreProcessor', []],
|
||||
['JDBCPostProcessor', []],
|
||||
['Assertions', []],
|
||||
['Extract', []],
|
||||
['JmeterElement', []],
|
||||
['CustomizeReq', getDefaultSamplerMenu()],
|
||||
['MaxSamplerProxy', getDefaultSamplerMenu()],
|
||||
['GenericController', getAll()],
|
||||
['SpecialSteps', ['HTTPSamplerProxy', 'Assertions', 'DubboSampler', 'JDBCSampler', 'TCPSampler', 'Sampler', 'AbstractSampler', 'JSR223Processor', 'API', 'MsUiCommand']],
|
||||
['AllSamplerProxy', ['GenericController','HTTPSamplerProxy', 'DubboSampler', 'JDBCSampler', 'TCPSampler', 'Sampler', 'AbstractSampler', 'JSR223Processor', 'API', 'MsUiCommand']],
|
||||
['DEFINITION', ['HTTPSamplerProxy', 'DubboSampler', 'JDBCSampler', 'TCPSampler']],
|
||||
['ALlSamplerStep', ['JSR223PreProcessor', 'JSR223PostProcessor', 'JDBCPreProcessor', 'JDBCPostProcessor', 'Assertions', 'Extract', 'ConstantTimer']],
|
||||
['AllCanExecType', ['HTTPSamplerProxy', 'DubboSampler', 'JDBCSampler', 'TCPSampler', 'JSR223Processor', 'AbstractSampler']]]);
|
||||
return map
|
||||
}
|
||||
|
||||
export const ELEMENT_TYPE = {
|
||||
scenario: 'scenario',
|
||||
HTTPSamplerProxy: 'HTTPSamplerProxy',
|
||||
OT_IMPORT: 'OT_IMPORT',
|
||||
IfController: 'IfController',
|
||||
TransactionController: 'TransactionController',
|
||||
ConstantTimer: 'ConstantTimer',
|
||||
JSR223Processor: 'JSR223Processor',
|
||||
JSR223PreProcessor: 'JSR223PreProcessor',
|
||||
JSR223PostProcessor: 'JSR223PostProcessor',
|
||||
JDBCPostProcessor: 'JDBCPostProcessor',
|
||||
JDBCPreProcessor: 'JDBCPreProcessor',
|
||||
Assertions: 'Assertions',
|
||||
Extract: 'Extract',
|
||||
CustomizeReq: 'CustomizeReq',
|
||||
LoopController: 'LoopController',
|
||||
Plugin: 'Plugin'
|
||||
}
|
||||
|
||||
export const TYPE_TO_C = new Map([
|
||||
['scenario', 'io.metersphere.hashtree.MsScenario'],
|
||||
['customCommand', 'io.metersphere.xpack.ui.hashtree.MsUiCustomCommand'],
|
||||
['UiScenario', 'io.metersphere.hashtree.MsUiScenario'],
|
||||
['HTTPSamplerProxy', 'io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy'],
|
||||
['DubboSampler', 'io.metersphere.api.dto.definition.request.sampler.MsDubboSampler'],
|
||||
['JDBCSampler', 'io.metersphere.api.dto.definition.request.sampler.MsJDBCSampler'],
|
||||
['TCPSampler', 'io.metersphere.api.dto.definition.request.sampler.MsTCPSampler'],
|
||||
['IfController', 'io.metersphere.dto.request.controller.MsIfController'],
|
||||
['TransactionController', 'io.metersphere.dto.request.controller.MsTransactionController'],
|
||||
['LoopController', 'io.metersphere.dto.request.controller.MsLoopController'],
|
||||
['ConstantTimer', 'io.metersphere.dto.request.timer.MsConstantTimer'],
|
||||
['JSR223Processor', 'io.metersphere.api.dto.definition.request.processors.MsJSR223Processor'],
|
||||
['JSR223PreProcessor', 'io.metersphere.api.dto.definition.request.processors.pre.MsJSR223PreProcessor'],
|
||||
['JSR223PostProcessor', 'io.metersphere.api.dto.definition.request.processors.post.MsJSR223PostProcessor'],
|
||||
['JDBCPreProcessor', 'io.metersphere.api.dto.definition.request.processors.pre.MsJDBCPreProcessor'],
|
||||
['JDBCPostProcessor', 'io.metersphere.api.dto.definition.request.processors.post.MsJDBCPostProcessor'],
|
||||
['Assertions', 'io.metersphere.api.dto.definition.request.assertions.MsAssertions'],
|
||||
['Extract', 'io.metersphere.dto.request.extract.MsExtract'],
|
||||
['JmeterElement', 'io.metersphere.dto.request.unknown.MsJmeterElement'],
|
||||
['TestPlan', 'io.metersphere.hashtree.MsTestPlan'],
|
||||
['ThreadGroup', 'io.metersphere.hashtree.MsThreadGroup'],
|
||||
['DNSCacheManager', 'io.metersphere.api.dto.definition.request.dns.MsDNSCacheManager'],
|
||||
['DebugSampler', 'io.metersphere.hashtree.MsDebugSampler'],
|
||||
['AuthManager', 'io.metersphere.dto.request.auth.MsAuthManager']
|
||||
])
|
||||
|
||||
export const PLUGIN_ELEMENTS = new Map([
|
||||
['menu_post_processors', ['HtmlExtractor', 'JMESPathExtractor', 'JSONPostProcessor', 'RegexExtractor', 'BoundaryExtractor', 'Separator', 'XPath2Extractor', 'XPathExtractor', 'ResultAction', 'DebugPostProcessor', 'BeanShellPostProcessor']],
|
||||
['menu_assertions', ['JSONPathAssertion', 'SizeAssertion', 'JSR223Assertion', 'XPath2Assertion', 'Separator', 'HTMLAssertion', 'JMESPathAssertion', 'MD5HexAssertion', 'SMIMEAssertion', 'XMLSchemaAssertion', 'XMLAssertion', 'XPathAssertion', 'DurationAssertion', 'CompareAssertion', 'BeanShellAssertion']],
|
||||
['menu_listener', ['AbstractVisualizer', 'AbstractListener', 'ViewResultsFullVisualizer', 'SummaryReport', 'StatVisualizer', 'BackendListener', 'Separator', 'JSR223Listener', 'ResultSaver', 'RespTimeGraphVisualizer', 'GraphVisualizer', 'AssertionVisualizer', 'ComparisonVisualizer', 'StatGraphVisualizer', 'Summariser', 'TableVisualizer', 'SimpleDataWriter', 'MailerVisualizer', 'BeanShellListener']],
|
||||
['menu_pre_processors', ['AbstractPostProcessor', 'UserParameters', 'Separator', 'AnchorModifier', 'URLRewritingModifier', 'SampleTimeout', 'RegExUserParameters', 'BeanShellPreProcessor']],
|
||||
['menu_logic_controller', ['GenericController', 'scenario', 'IfController', 'LoopController', 'IfControllerPanel', 'TransactionController', 'LoopControlPanel', 'WhileController', 'Separator', 'ForeachControlPanel', 'IncludeController', 'RunTime', 'CriticalSectionController', 'InterleaveControl', 'OnceOnlyController', 'RecordController', 'LogicController', 'RandomControl', 'RandomOrderController', 'ThroughputController', 'SwitchController', 'ModuleController']],
|
||||
['menu_fragments', ['TestFragmentController']],
|
||||
['menu_non_test_elements', ['ProxyControl', 'HttpMirrorControl', 'GenerateTree', 'PropertyControl']],
|
||||
['menu_generative_controller', ['HTTPSamplerProxy', 'JSR223Processor', 'DubboSampler', 'JDBCSampler', 'TCPSampler', 'Sampler', 'AbstractSampler', 'CustomizeReq', 'HttpTestSample', 'TestAction', 'DebugSampler', 'JSR223Sampler', 'Separator', 'AjpSampler', 'AccessLogSampler', 'BeanShellSampler', 'BoltSampler', 'FtpTestSampler', 'GraphQLHTTPSampler', 'JDBCSampler', 'JMSPublisher', 'JMSSampler', 'JMSSubscriber', 'JUnitTestSampler', 'JavaTestSampler', 'LdapExtTestSampler', 'LdapTestSampler', 'SystemSampler', 'SmtpSampler', 'TCPSampler', 'MailReaderSampler']],
|
||||
['menu_threads', ['SetupThreadGroup', 'PostThreadGroup', 'ThreadGroup']],
|
||||
['menu_timer', ['ConstantTimer', 'UniformRandomTimer', 'PreciseThroughputTimer', 'ConstantThroughputTimer', 'Separator', 'JSR223Timer', 'SyncTimer', 'PoissonRandomTimer', 'GaussianRandomTimer', 'BeanShellTimer']],
|
||||
['menu_config_element', ['CSVDataSet', 'HeaderPanel', 'CookiePanel', 'CacheManager', 'HttpDefaults', 'Separator', 'BoltConnectionElement', 'DNSCachePanel', 'FtpConfig', 'AuthPanel', 'DataSourceElement', 'JavaConfig', 'LdapExtConfig', 'LdapConfig', 'TCPConfig', 'KeystoreConfig', 'ArgumentsPanel', 'LoginConfig', 'SimpleConfig', 'CounterConfig', 'RandomVariableConfig']],
|
||||
])
|
||||
|
||||
export function getDefaultSamplerMenu() {
|
||||
let array = [];
|
||||
array = array.concat(PLUGIN_ELEMENTS.get('menu_assertions'));
|
||||
array = array.concat(PLUGIN_ELEMENTS.get('menu_pre_processors'));
|
||||
array = array.concat(PLUGIN_ELEMENTS.get('menu_post_processors'));
|
||||
array = array.concat(PLUGIN_ELEMENTS.get('menu_config_element'));
|
||||
array = array.concat(PLUGIN_ELEMENTS.get('menu_listener'));
|
||||
return array;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
let allArray = [];
|
||||
allArray = allArray.concat(PLUGIN_ELEMENTS.get('menu_generative_controller'));
|
||||
allArray = allArray.concat(PLUGIN_ELEMENTS.get('menu_logic_controller'));
|
||||
allArray = allArray.concat(['scenario', 'ConstantTimer', 'JSR223Processor', 'Assertions']);
|
||||
return allArray;
|
||||
}
|
||||
|
||||
export function getAll() {
|
||||
let allArray = [];
|
||||
allArray = allArray.concat(getDefaultSamplerMenu());
|
||||
allArray = allArray.concat(PLUGIN_ELEMENTS.get('menu_logic_controller'));
|
||||
allArray = allArray.concat(PLUGIN_ELEMENTS.get('menu_non_test_elements'));
|
||||
allArray = allArray.concat(PLUGIN_ELEMENTS.get('menu_generative_controller'));
|
||||
allArray = allArray.concat(PLUGIN_ELEMENTS.get('menu_threads'));
|
||||
return allArray;
|
||||
}
|
|
@ -0,0 +1,284 @@
|
|||
<template>
|
||||
<el-card class="ms-cards">
|
||||
<div class="request-result">
|
||||
<div @click="active">
|
||||
<el-row :gutter="16" type="flex" align="middle" class="info">
|
||||
<el-col class="ms-req-name-col" :span="18" v-if="indexNumber != undefined">
|
||||
<div class="method ms-req-name">
|
||||
<div class="el-step__icon is-text ms-api-col-create">
|
||||
<div class="el-step__icon-inner"> {{ indexNumber }}</div>
|
||||
</div>
|
||||
<i class="icon el-icon-arrow-right" :class="{'is-active': showActive}" @click="active" @click.stop/>
|
||||
<span>{{ label }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="3">
|
||||
<span v-if="!isUnexecute" :style="result && !result.success ? 'color: #FE6F71' : ''">
|
||||
{{ result.time }} ms
|
||||
</span>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="3">
|
||||
<!-- 兼容旧数据 报告截图 -->
|
||||
<el-popover
|
||||
placement="right"
|
||||
trigger="hover"
|
||||
popper-class="issues-popover"
|
||||
v-if="!isUnexecute && result.uiImg">
|
||||
<el-image
|
||||
style="width: 100px; height: 100px"
|
||||
:src="'/resource/ui/get?fileName=' + result.uiImg + '&reportId=' + result.reportId"
|
||||
:preview-src-list="['/resource/ui/get?fileName=' + result.uiImg + '&reportId=' + result.reportId]">
|
||||
</el-image>
|
||||
<el-button slot="reference" type="text">{{ $t('ui.screenshot') }}</el-button>
|
||||
</el-popover>
|
||||
|
||||
<div @click.stop="triggerViewer" v-if="!isUnexecute && uiScreenshots && result.combinationImg"
|
||||
style="color: #783887;"> {{ $t('ui.screenshot') }}
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="2">
|
||||
<div>
|
||||
<el-tag size="mini" v-if="isUnexecute">
|
||||
{{ $t('api_test.home_page.detail_card.unexecute') }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="success" v-else-if="result && result.success">
|
||||
{{ $t('api_report.success') }}
|
||||
</el-tag>
|
||||
<el-tag v-else size="mini" type="danger">
|
||||
{{ $t('api_report.fail') }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<el-collapse-transition>
|
||||
<div v-show="showActive" style="width: 99%">
|
||||
<ui-command-result-detail
|
||||
v-loading="detail.loading"
|
||||
:result="detail"
|
||||
/>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
<ui-screenshot-viewer ref="screenshotViewer" v-if="!isUnexecute && uiScreenshots && result.combinationImg"
|
||||
:src="'/resource/ui/get?fileName=' + result.combinationImg + '&reportId=' + result.reportId"/>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UiCommandResultDetail from "./UiCommandResultDetail";
|
||||
import UiScreenshotViewer from "./UiScreenshotViewer";
|
||||
|
||||
export default {
|
||||
name: "UiCommandResult",
|
||||
components: {UiCommandResultDetail, UiScreenshotViewer},
|
||||
props: {
|
||||
indexNumber: Number,
|
||||
result: Object,
|
||||
command: Object,
|
||||
stepId: String,
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
treeNode: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showActive: false,
|
||||
detail: {
|
||||
loading: false
|
||||
},
|
||||
uiScreenshots: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.result.uiScreenshots) {
|
||||
this.uiScreenshots = this.result.uiScreenshots || [];
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
label() {
|
||||
if (!this.$t("ui." + this.command.label).startsWith("ui")) {
|
||||
return this.$t("ui." + this.command.label);
|
||||
}
|
||||
return this.command.label;
|
||||
},
|
||||
isUnexecute() {
|
||||
return !this.result || this.result.status === 'unexecute';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isActive() {
|
||||
this.showActive = this.isActive;
|
||||
},
|
||||
errorCode() {
|
||||
this.baseErrorCode = this.errorCode;
|
||||
},
|
||||
'treeNode.expanded'() {
|
||||
if (this.treeNode.expanded) {
|
||||
this.loadRequestInfoExpand();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
triggerViewer() {
|
||||
this.$refs.screenshotViewer.open();
|
||||
},
|
||||
active() {
|
||||
if (this.isUnexecute) {
|
||||
this.showActive = false;
|
||||
} else {
|
||||
this.showActive = !this.showActive;
|
||||
}
|
||||
if (this.showActive) {
|
||||
this.loadRequestInfoExpand();
|
||||
}
|
||||
},
|
||||
loadRequestInfoExpand() {
|
||||
if (this.command && this.command.value) {
|
||||
if (!this.command.value.time && this.command.value.startTime && this.command.value.endTime) {
|
||||
this.command.value.time = this.command.value.endTime - this.command.value.startTime;
|
||||
}
|
||||
this.result = this.command.value;
|
||||
this.detail = this.command.value;
|
||||
} else {
|
||||
if (!this.detail.hasData) {
|
||||
this.detail.loading = true;
|
||||
this.$get("/ui/automation/selectReportContent/" + this.stepId).then(response => {
|
||||
let requestResult = response.data;
|
||||
if (requestResult) {
|
||||
this.detail = requestResult;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.detail.loading = false;
|
||||
this.detail.hasData = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-result {
|
||||
min-height: 30px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.request-result .info {
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.request-result .method {
|
||||
color: #1E90FF;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 35px;
|
||||
padding-left: 5px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.ms-cards :deep(.el-card__body) {
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.sub-result:last-child {
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.ms-test-running {
|
||||
color: #783887;
|
||||
}
|
||||
|
||||
.ms-test-error_code {
|
||||
color: #F6972A;
|
||||
background-color: #FDF5EA;
|
||||
border-color: #FDF5EA;
|
||||
}
|
||||
|
||||
.ms-api-col {
|
||||
background-color: #EFF0F0;
|
||||
border-color: #EFF0F0;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
color: #64666A;
|
||||
}
|
||||
|
||||
.ms-api-col-create {
|
||||
background-color: #EBF2F2;
|
||||
border-color: #008080;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
color: #008080;
|
||||
}
|
||||
|
||||
:deep(.el-step__icon) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.el-divider--horizontal {
|
||||
margin: 2px 0;
|
||||
background: 0 0;
|
||||
border-top: 1px solid #e8eaec;
|
||||
}
|
||||
|
||||
.ms-req-name {
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
padding-bottom: 0;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.ms-req-name-col {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,175 @@
|
|||
<template>
|
||||
<div class="request-result">
|
||||
<div>
|
||||
<el-row type="flex" justify="end" class="info">
|
||||
<el-col :span="24">
|
||||
<div class="time">
|
||||
{{ $t('api_report.start_time') }}:{{ result.startTime | timestampFormatDate(true) }}
|
||||
{{ $t('report.test_end_time') }}:{{ result.endTime | timestampFormatDate(true) }}
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<div v-show="isActive">
|
||||
<div class="text-container">
|
||||
<el-collapse-transition>
|
||||
<el-tabs value="body" v-show="isActive">
|
||||
<el-tab-pane :label="$t('ui.log')" name="body" class="pane">
|
||||
<div class="ms-div">
|
||||
<pre>{{ getBody() }}</pre>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_report.assertions')" name="assertions" class="pane assertions">
|
||||
<ms-assertion-results :show-content="false" :assertions="result.assertions"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.request.extract.label')" name="label" class="pane">
|
||||
<pre>{{ result.vars }}</pre>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "UiCommandResultDetail",
|
||||
components: {
|
||||
},
|
||||
props: {
|
||||
request: Object,
|
||||
scenarioName: String,
|
||||
requestType: String,
|
||||
console: String,
|
||||
result: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: true,
|
||||
isCodeEditAlive: true,
|
||||
modes: ['text', 'json', 'xml', 'html'],
|
||||
sqlModes: ['text', 'table'],
|
||||
mode: 'text'
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
reload() {
|
||||
this.isCodeEditAlive = false;
|
||||
this.$nextTick(() => (this.isCodeEditAlive = true));
|
||||
},
|
||||
modeChange(mode) {
|
||||
this.mode = mode;
|
||||
},
|
||||
sqlModeChange(mode) {
|
||||
this.mode = mode;
|
||||
},
|
||||
getBody(){
|
||||
if (!this.result.success) {
|
||||
return this.result.body ? this.result.body : ""
|
||||
} else {
|
||||
return "OK";
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
'request.responseResult'() {
|
||||
this.reload();
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
assertion() {
|
||||
return this.request.passAssertions + " / " + this.request.totalAssertions;
|
||||
},
|
||||
hasSub() {
|
||||
return this.request.subRequestResults.length > 0;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.request-result .info {
|
||||
background-color: #F9F9F9;
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.request-result {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.request-result .time {
|
||||
color: #7f7f7f;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-top: 4px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.time {
|
||||
text-align: end;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<div class="el-image-viewer__wrapper" style="z-index: 999" v-show="active">
|
||||
<div class="viewer-mask">
|
||||
<div class="el-image-viewer__canvas">
|
||||
<div class="image-wrap">
|
||||
<el-image
|
||||
fit="contain"
|
||||
:src="src"
|
||||
class="img-content"
|
||||
></el-image>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="el-image-viewer__btn el-image-viewer__close"
|
||||
style="color: #fff"
|
||||
@click.stop="close"
|
||||
><i class="el-icon-close"></i
|
||||
></span>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "UiScreenshotViewer",
|
||||
props:['src'],
|
||||
data() {
|
||||
return {
|
||||
active: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.active = true;
|
||||
},
|
||||
close() {
|
||||
this.active = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.image-wrap {
|
||||
width: 80%;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.viewer-mask {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.img-content.el-image {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,762 @@
|
|||
<template>
|
||||
<ms-container v-loading="loading">
|
||||
<ms-main-container class="api-report-content">
|
||||
<el-card class="report-body">
|
||||
<section class="report-container" v-if="this.report.testId">
|
||||
<!-- header -->
|
||||
<ms-api-report-view-header
|
||||
:show-cancel-button="showCancelButton"
|
||||
:show-rerun-button="showRerunButton"
|
||||
:is-plan="isPlan"
|
||||
:is-template="isTemplate"
|
||||
:debug="debug"
|
||||
:report="report"
|
||||
:project-env-map="projectEnvMap"
|
||||
@reportExport="handleExport"
|
||||
@reportSave="handleSave"/>
|
||||
|
||||
<!-- content -->
|
||||
<main v-if="isNotRunning">
|
||||
<!-- content header chart -->
|
||||
<ms-metric-chart :content="content" :totalTime="totalTime" :report="report"/>
|
||||
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||
<!-- all step-->
|
||||
<el-tab-pane :label="$t('api_report.total')" name="total">
|
||||
<ms-scenario-results
|
||||
:treeData="fullTreeNodes"
|
||||
:console="content.console"
|
||||
:report="report"
|
||||
:is-share="isShare"
|
||||
:share-id="shareId"
|
||||
v-on:requestResult="requestResult"
|
||||
ref="resultsTree"/>
|
||||
</el-tab-pane>
|
||||
<!-- fail step -->
|
||||
<el-tab-pane name="fail">
|
||||
<template slot="label">
|
||||
<span class="fail">{{ $t('api_report.fail') }}</span>
|
||||
</template>
|
||||
<ms-scenario-results
|
||||
v-on:requestResult="requestResult"
|
||||
:console="content.console"
|
||||
:report="report"
|
||||
:is-share="isShare"
|
||||
:share-id="shareId"
|
||||
:treeData="fullTreeNodes" ref="failsTree"
|
||||
:errorReport="content.error"/>
|
||||
</el-tab-pane>
|
||||
<!--error step -->
|
||||
<el-tab-pane name="errorReport" v-if="content.errorCode > 0">
|
||||
<template slot="label">
|
||||
<span class="fail" style="color: #F6972A">
|
||||
{{ $t('error_report_library.option.name') }}
|
||||
</span>
|
||||
</template>
|
||||
<ms-scenario-results
|
||||
v-on:requestResult="requestResult"
|
||||
:report="report"
|
||||
:is-share="isShare"
|
||||
:share-id="shareId"
|
||||
:console="content.console"
|
||||
:treeData="fullTreeNodes" ref="errorReportTree"/>
|
||||
</el-tab-pane>
|
||||
<!-- Not performed step -->
|
||||
<el-tab-pane name="unExecute" v-if="content.unExecute > 0">
|
||||
<template slot="label">
|
||||
<span class="fail"
|
||||
style="color: #9C9B9A">
|
||||
{{ $t('api_test.home_page.detail_card.unexecute') }}
|
||||
</span>
|
||||
</template>
|
||||
<ms-scenario-results
|
||||
v-on:requestResult="requestResult"
|
||||
:report="report"
|
||||
:is-share="isShare"
|
||||
:share-id="shareId"
|
||||
:console="content.console"
|
||||
:treeData="fullTreeNodes" ref="unExecuteTree"/>
|
||||
</el-tab-pane>
|
||||
<!-- console -->
|
||||
<el-tab-pane name="console">
|
||||
<template slot="label">
|
||||
<span class="console">{{ $t('api_test.definition.request.console') }}</span>
|
||||
</template>
|
||||
<ms-code-edit
|
||||
:mode="'text'"
|
||||
:read-only="true"
|
||||
:data.sync="content.console"
|
||||
height="calc(100vh - 500px)"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</main>
|
||||
</section>
|
||||
</el-card>
|
||||
</ms-main-container>
|
||||
</ms-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import MsRequestResult from "./RequestResult";
|
||||
import MsRequestResultTail from "./RequestResultTail";
|
||||
import MsScenarioResult from "./ScenarioResult";
|
||||
import MsMetricChart from "./MetricChart";
|
||||
import MsScenarioResults from "./ScenarioResults";
|
||||
import MsContainer from "metersphere-frontend/src/components/MsContainer";
|
||||
import MsMainContainer from "metersphere-frontend/src/components/MsMainContainer";
|
||||
import MsApiReportViewHeader from "./ApiReportViewHeader";
|
||||
import {STEP} from "./Setting";
|
||||
import MsCodeEdit from "metersphere-frontend/src/components/MsCodeEdit";
|
||||
import {getCurrentProjectID, getUUID, hasLicense, windowPrint} from "@/business/utils/sdk-utils";
|
||||
import {getScenarioReport, getScenarioReportAll, getShareScenarioReport} from "@/api/ui-report";
|
||||
|
||||
export default {
|
||||
name: "UiShareReportDetail",
|
||||
components: {
|
||||
MsApiReportViewHeader,
|
||||
MsMainContainer,
|
||||
MsCodeEdit,
|
||||
MsContainer, MsScenarioResults, MsRequestResultTail, MsMetricChart, MsScenarioResult, MsRequestResult
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeName: "total",
|
||||
content: {},
|
||||
report: {},
|
||||
loading: true,
|
||||
fails: [],
|
||||
failsTreeNodes: [],
|
||||
totalTime: 0,
|
||||
isRequestResult: false,
|
||||
request: {},
|
||||
isActive: false,
|
||||
scenarioName: null,
|
||||
reportExportVisible: false,
|
||||
fullTreeNodes: [],
|
||||
showRerunButton: false,
|
||||
stepFilter: new STEP,
|
||||
exportReportIsOk: false,
|
||||
tempResult: [],
|
||||
projectEnvMap: {},
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.isRequestResult = false;
|
||||
},
|
||||
props: {
|
||||
reportId: String,
|
||||
currentProjectId: String,
|
||||
infoDb: Boolean,
|
||||
debug: Boolean,
|
||||
isTemplate: Boolean,
|
||||
templateReport: Object,
|
||||
isShare: Boolean,
|
||||
shareId: String,
|
||||
isPlan: Boolean,
|
||||
showCancelButton: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
reportId() {
|
||||
if (!this.isTemplate) {
|
||||
this.getReport();
|
||||
}
|
||||
},
|
||||
templateReport() {
|
||||
if (this.isTemplate) {
|
||||
this.getReport();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
filter(index) {
|
||||
if (index === "1") {
|
||||
this.$refs.failsTree.filter(index);
|
||||
} else if (this.activeName === "errorReport") {
|
||||
this.$refs.errorReportTree.filter("errorReport");
|
||||
} else if (this.activeName === "unExecute") {
|
||||
this.$refs.unExecuteTree.filter("unexecute");
|
||||
}
|
||||
},
|
||||
init() {
|
||||
this.loading = true;
|
||||
this.projectEnvMap = {};
|
||||
this.content = {};
|
||||
this.fails = [];
|
||||
this.report = {};
|
||||
this.fullTreeNodes = [];
|
||||
this.failsTreeNodes = [];
|
||||
this.isRequestResult = false;
|
||||
this.activeName = "total";
|
||||
this.showRerunButton = false;
|
||||
},
|
||||
rerunVerify() {
|
||||
if (hasLicense() && this.fullTreeNodes && this.fullTreeNodes.length > 0 && !this.isShare) {
|
||||
this.fullTreeNodes.forEach(item => {
|
||||
item.redirect = true;
|
||||
if (item.totalStatus === 'fail' || item.totalStatus === 'error' || item.unExecuteTotal > 0
|
||||
|| (item.type === "API" && item.totalStatus === 'unexecute')) {
|
||||
this.showRerunButton = true;
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
handleClick(tab, event) {
|
||||
this.isRequestResult = false;
|
||||
if (this.report && this.report.reportVersion && this.report.reportVersion > 1) {
|
||||
this.filter(tab.index);
|
||||
}
|
||||
},
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
formatResult(res) {
|
||||
let resMap = new Map;
|
||||
let array = [];
|
||||
if (res && res.scenarios) {
|
||||
res.scenarios.forEach(item => {
|
||||
if (item && item.requestResults) {
|
||||
item.requestResults.forEach(req => {
|
||||
req.responseResult.console = res.console;
|
||||
resMap.set(req.id + req.name, req);
|
||||
req.name = item.name + "^@~@^" + req.name + "UUID=" + getUUID();
|
||||
array.push(req);
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
this.formatTree(array, this.fullTreeNodes);
|
||||
this.sort(this.fullTreeNodes);
|
||||
this.$emit('refresh', resMap);
|
||||
},
|
||||
formatTree(array, tree) {
|
||||
array.map((item) => {
|
||||
let key = item.name;
|
||||
let nodeArray = key.split('^@~@^');
|
||||
let children = tree;
|
||||
//运行场景中如果连续将1个场景引入多次,会出现运行结果合并的情况。
|
||||
//为了解决这种问题,在转hashTree的时候给场景放了个新ID,前台加载解析的时候也要做处理
|
||||
let scenarioId = "";
|
||||
let scenarioName = "";
|
||||
if (item.scenario) {
|
||||
let scenarioArr = JSON.parse(item.scenario);
|
||||
if (scenarioArr.length > 1) {
|
||||
let scenarioIdArr = scenarioArr[0].split("_");
|
||||
scenarioId = scenarioIdArr[0];
|
||||
scenarioName = scenarioIdArr[1];
|
||||
}
|
||||
}
|
||||
// 循环构建子节点
|
||||
for (let i = 0; i < nodeArray.length; i++) {
|
||||
if (!nodeArray[i]) {
|
||||
continue;
|
||||
}
|
||||
let node = {
|
||||
label: nodeArray[i],
|
||||
value: item,
|
||||
};
|
||||
if (i !== (nodeArray.length - 1)) {
|
||||
node.children = [];
|
||||
} else {
|
||||
if (item.subRequestResults && item.subRequestResults.length > 0) {
|
||||
let itemChildren = this.deepFormatTreeNode(item.subRequestResults);
|
||||
node.children = itemChildren;
|
||||
if (node.label.indexOf("UUID=")) {
|
||||
node.label = node.label.split("UUID=")[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (children.length === 0) {
|
||||
children.push(node);
|
||||
}
|
||||
|
||||
let isExist = false;
|
||||
for (let j in children) {
|
||||
if (children[j].label === node.label) {
|
||||
|
||||
let idIsPath = true;
|
||||
//判断ID是否匹配 目前发现问题的只有重复场景,而重复场景是在第二个节点开始合并的。所以这里暂时只判断第二个场景问题。
|
||||
//如果出现了其他问题,则需要检查其他问题的数据结构。暂时采用具体问题具体分析的策略
|
||||
if (i === nodeArray.length - 2) {
|
||||
idIsPath = false;
|
||||
let childId = "";
|
||||
let childName = "";
|
||||
if (children[j].value && children[j].value.scenario) {
|
||||
let scenarioArr = JSON.parse(children[j].value.scenario);
|
||||
if (scenarioArr.length > 1) {
|
||||
let childArr = scenarioArr[0].split("_");
|
||||
childId = childArr[0];
|
||||
if (childArr.length > 1) {
|
||||
childName = childArr[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scenarioId === "") {
|
||||
idIsPath = true;
|
||||
} else if (scenarioId === childId) {
|
||||
idIsPath = true;
|
||||
} else if (scenarioName !== childName) {
|
||||
//如果两个名字不匹配则默认通过,不匹配ID
|
||||
idIsPath = true;
|
||||
}
|
||||
}
|
||||
if (idIsPath) {
|
||||
if (i !== nodeArray.length - 1 && !children[j].children) {
|
||||
children[j].children = [];
|
||||
}
|
||||
children = (i === nodeArray.length - 1 ? children : children[j].children);
|
||||
isExist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isExist) {
|
||||
children.push(node);
|
||||
if (i !== nodeArray.length - 1 && !children[children.length - 1].children) {
|
||||
children[children.length - 1].children = [];
|
||||
}
|
||||
children = (i === nodeArray.length - 1 ? children : children[children.length - 1].children);
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
deepFormatTreeNode(array) {
|
||||
let returnChildren = [];
|
||||
array.map((item) => {
|
||||
let children = [];
|
||||
let key = item.name.split('^@~@^')[0];
|
||||
let nodeArray = key.split('<->');
|
||||
//运行场景中如果连续将1个场景引入多次,会出现运行结果合并的情况。
|
||||
//为了解决这种问题,在转hashTree的时候给场景放了个新ID,前台加载解析的时候也要做处理
|
||||
let scenarioId = "";
|
||||
let scenarioName = "";
|
||||
if (item.scenario) {
|
||||
let scenarioArr = JSON.parse(item.scenario);
|
||||
if (scenarioArr.length > 1) {
|
||||
let scenarioIdArr = scenarioArr[0].split("_");
|
||||
scenarioId = scenarioIdArr[0];
|
||||
scenarioName = scenarioIdArr[1];
|
||||
}
|
||||
}
|
||||
// 循环构建子节点
|
||||
let node = {
|
||||
label: nodeArray[0],
|
||||
value: item,
|
||||
children: []
|
||||
};
|
||||
if (item.subRequestResults && item.subRequestResults.length > 0) {
|
||||
let itemChildren = this.deepFormatTreeNode(item.subRequestResults);
|
||||
node.children = itemChildren;
|
||||
}
|
||||
children.push(node);
|
||||
children.forEach(itemNode => {
|
||||
returnChildren.push(itemNode);
|
||||
});
|
||||
|
||||
});
|
||||
return returnChildren;
|
||||
},
|
||||
recursiveSorting(arr) {
|
||||
for (let i in arr) {
|
||||
if (arr[i]) {
|
||||
arr[i].index = Number(i) + 1;
|
||||
if (arr[i].children && arr[i].children.length > 0) {
|
||||
this.recursiveSorting(arr[i].children);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
sort(scenarioDefinition) {
|
||||
for (let i in scenarioDefinition) {
|
||||
// 排序
|
||||
if (scenarioDefinition[i]) {
|
||||
scenarioDefinition[i].index = Number(i) + 1;
|
||||
if (scenarioDefinition[i].children && scenarioDefinition[i].children.length > 0) {
|
||||
this.recursiveSorting(scenarioDefinition[i].children);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
getReportByExport() {
|
||||
if (this.exportReportIsOk) {
|
||||
this.startExport();
|
||||
} else {
|
||||
getScenarioReportAll(this.reportId).then(data => {
|
||||
if (data && data.data.content) {
|
||||
let report = JSON.parse(data.data.content);
|
||||
if (report.projectEnvMap) {
|
||||
this.projectEnvMap = report.projectEnvMap;
|
||||
}
|
||||
this.content = report;
|
||||
this.fullTreeNodes = report.steps;
|
||||
this.content.console = report.console;
|
||||
this.content.error = report.error;
|
||||
let successCount = (report.total - report.error - report.errorCode - report.unExecute);
|
||||
this.content.success = successCount;
|
||||
this.totalTime = report.totalTime;
|
||||
}
|
||||
this.exportReportIsOk = true;
|
||||
setTimeout(this.startExport, 500)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getReport() {
|
||||
this.init();
|
||||
if (this.isTemplate) {
|
||||
// 测试计划报告导出
|
||||
if (this.templateReport) {
|
||||
this.handleGetScenarioReport(this.templateReport);
|
||||
} else {
|
||||
this.report = this.templateReport;
|
||||
this.buildReport();
|
||||
}
|
||||
} else if (this.isShare) {
|
||||
if (this.reportId) {
|
||||
getShareScenarioReport(this.shareId, this.reportId).then(data => {
|
||||
this.checkReport(data.data);
|
||||
this.handleGetScenarioReport(data.data);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
getScenarioReport(this.reportId).then(data => {
|
||||
this.checkReport(data.data);
|
||||
this.handleGetScenarioReport(data.data);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
checkReport(data) {
|
||||
if (!data) {
|
||||
this.$emit('reportNotExist');
|
||||
}
|
||||
},
|
||||
handleGetScenarioReport(data) {
|
||||
if (data) {
|
||||
this.report = data;
|
||||
if (this.report.reportVersion && this.report.reportVersion > 1) {
|
||||
this.report.status = data.status;
|
||||
if (!this.isNotRunning) {
|
||||
setTimeout(this.getReport, 2000)
|
||||
} else {
|
||||
if (data.content) {
|
||||
let report = JSON.parse(data.content);
|
||||
this.content = report;
|
||||
if (report.projectEnvMap) {
|
||||
this.projectEnvMap = report.projectEnvMap;
|
||||
}
|
||||
if (data.reportType === "UI_INDEPENDENT") {
|
||||
this.tempResult = report.steps;
|
||||
//校对执行次序
|
||||
try {
|
||||
this.checkOrder(this.tempResult);
|
||||
this.fullTreeNodes = this.tempResult;
|
||||
} catch (e) {
|
||||
this.fullTreeNodes = report.steps;
|
||||
}
|
||||
} else {
|
||||
this.fullTreeNodes = report.steps;
|
||||
}
|
||||
this.content.console = report.console;
|
||||
this.content.error = report.error;
|
||||
let successCount = (report.total - report.error - report.errorCode - report.unExecute);
|
||||
this.content.success = successCount;
|
||||
this.totalTime = report.totalTime;
|
||||
}
|
||||
// 增加失败重跑校验
|
||||
if (this.report && this.report.reportType === 'SCENARIO_INTEGRATED' || this.report.reportType === 'API_INTEGRATED') {
|
||||
this.rerunVerify();
|
||||
}
|
||||
this.loading = false;
|
||||
}
|
||||
} else {
|
||||
this.buildReport();
|
||||
}
|
||||
} else {
|
||||
this.$emit('invisible');
|
||||
this.$warning(this.$t('commons.report_delete'));
|
||||
}
|
||||
},
|
||||
checkOrder(origin) {
|
||||
if (!origin) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(origin)) {
|
||||
this.sortChildren(origin);
|
||||
origin.forEach(v => {
|
||||
if (v.children) {
|
||||
this.checkOrder(v.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
sortChildren(source) {
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
source.forEach(item => {
|
||||
let children = item.children;
|
||||
if (children && children.length > 0) {
|
||||
let tempArr = new Array(children.length);
|
||||
let tempMap = new Map();
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
if (!children[i].value || !children[i].value.startTime || children[i].value.startTime === 0) {
|
||||
//若没有value或未执行的,则step留在当前位置
|
||||
tempArr[i] = children[i];
|
||||
//进行标识
|
||||
tempMap.set(children[i].stepId, children[i])
|
||||
}
|
||||
}
|
||||
|
||||
//过滤出还没有指定好位置的step
|
||||
let arr = children.filter(m => {
|
||||
return !tempMap.get(m.stepId);
|
||||
}).sort((m, n) => {
|
||||
//按时间排序
|
||||
return m.value.startTime - n.value.startTime;
|
||||
});
|
||||
|
||||
//找出arr(已经有序,从头取即可)中时间最小的插入 tempArr 可用位置
|
||||
for (let j = 0, i = 0; j < tempArr.length; j++) {
|
||||
if (!tempArr[j]) {
|
||||
//占位
|
||||
tempArr[j] = arr[i];
|
||||
i++;
|
||||
}
|
||||
//重新排序
|
||||
tempArr[j].index = j + 1;
|
||||
}
|
||||
|
||||
//赋值
|
||||
item.children = tempArr;
|
||||
}
|
||||
})
|
||||
},
|
||||
buildReport() {
|
||||
if (this.report) {
|
||||
if (this.isNotRunning) {
|
||||
|
||||
this.content = JSON.parse(this.report.content);
|
||||
if (!this.content) {
|
||||
this.content = {scenarios: []};
|
||||
}
|
||||
this.formatResult(this.content);
|
||||
this.getFails();
|
||||
this.computeTotalTime();
|
||||
this.loading = false;
|
||||
} else {
|
||||
setTimeout(this.getReport, 2000)
|
||||
}
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.$error(this.$t('api_report.not_exist'));
|
||||
}
|
||||
},
|
||||
getFails() {
|
||||
if (this.isNotRunning) {
|
||||
this.fails = [];
|
||||
let array = [];
|
||||
this.totalTime = 0
|
||||
if (this.content.scenarios) {
|
||||
this.content.scenarios.forEach((scenario) => {
|
||||
this.totalTime = this.totalTime + Number(scenario.responseTime)
|
||||
let failScenario = Object.assign({}, scenario);
|
||||
if (scenario.error > 0) {
|
||||
this.fails.push(failScenario);
|
||||
failScenario.requestResults = [];
|
||||
scenario.requestResults.forEach((request) => {
|
||||
if (!request.success) {
|
||||
let failRequest = Object.assign({}, request);
|
||||
failScenario.requestResults.push(failRequest);
|
||||
array.push(request);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
this.formatTree(array, this.failsTreeNodes);
|
||||
this.sort(this.failsTreeNodes);
|
||||
}
|
||||
},
|
||||
computeTotalTime() {
|
||||
if (this.content.scenarios) {
|
||||
let startTime = 0;
|
||||
let endTime = 0;
|
||||
let requestTime = 0;
|
||||
this.content.scenarios.forEach((scenario) => {
|
||||
scenario.requestResults.forEach((request) => {
|
||||
if (request.startTime && Number(request.startTime)) {
|
||||
startTime = request.startTime;
|
||||
}
|
||||
if (request.endTime && Number(request.endTime)) {
|
||||
endTime = request.endTime;
|
||||
}
|
||||
let resTime;
|
||||
if (startTime === 0 || endTime === 0) {
|
||||
resTime = 0
|
||||
} else {
|
||||
resTime = endTime - startTime
|
||||
}
|
||||
requestTime = requestTime + resTime;
|
||||
})
|
||||
})
|
||||
this.totalTime = requestTime
|
||||
}
|
||||
},
|
||||
requestResult(requestResult) {
|
||||
this.active();
|
||||
this.isRequestResult = false;
|
||||
this.$nextTick(function () {
|
||||
this.isRequestResult = true;
|
||||
this.request = requestResult.request;
|
||||
this.scenarioName = requestResult.scenarioName;
|
||||
});
|
||||
},
|
||||
formatExportApi(array, scenario) {
|
||||
array.forEach(item => {
|
||||
if (this.stepFilter && this.stepFilter.get("AllSamplerProxy").indexOf(item.type) !== -1) {
|
||||
if (item.errorCode) {
|
||||
item.value.errorCode = item.errorCode;
|
||||
}
|
||||
scenario.requestResults.push(item.value);
|
||||
}
|
||||
if (item.children && item.children.length > 0) {
|
||||
this.formatExportApi(item.children, scenario);
|
||||
}
|
||||
})
|
||||
},
|
||||
handleExport() {
|
||||
this.getReportByExport();
|
||||
},
|
||||
startExport() {
|
||||
if (this.report.reportVersion && this.report.reportVersion > 1) {
|
||||
if (this.report.reportType === 'API_INTEGRATED' || this.report.reportType === 'UI_INTEGRATED') {
|
||||
let scenario = {name: "", requestResults: []};
|
||||
this.content.scenarios = [scenario];
|
||||
this.formatExportApi(this.fullTreeNodes, scenario);
|
||||
} else {
|
||||
if (this.fullTreeNodes) {
|
||||
this.fullTreeNodes.forEach(item => {
|
||||
if (item.type === "scenario" || item.type === "UiScenario") {
|
||||
let scenario = {name: item.label, requestResults: []};
|
||||
if (this.content.scenarios && this.content.scenarios.length > 0) {
|
||||
this.content.scenarios.push(scenario);
|
||||
} else {
|
||||
this.content.scenarios = [scenario];
|
||||
}
|
||||
this.formatExportApi(item.children, scenario);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
this.reportExportVisible = true;
|
||||
let reset = this.exportReportReset;
|
||||
this.$nextTick(() => {
|
||||
windowPrint('apiTestReport', 0.57);
|
||||
reset();
|
||||
});
|
||||
},
|
||||
handleSave() {
|
||||
if (!this.report.name) {
|
||||
this.$warning(this.$t('api_test.automation.report_name_info'));
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
let url = "/ui/scenario/report/reName";
|
||||
this.result = this.$post(url, {
|
||||
id: this.report.id,
|
||||
name: this.report.name,
|
||||
reportType: this.report.reportType
|
||||
}, response => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.loading = false;
|
||||
this.$emit('refresh');
|
||||
}, error => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
exportReportReset() {
|
||||
this.$router.go(0);
|
||||
},
|
||||
handleProjectChange() {
|
||||
this.$router.push('/api/automation/report');
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.getReport();
|
||||
},
|
||||
destroyed() {
|
||||
},
|
||||
computed: {
|
||||
path() {
|
||||
return "/api/test/edit?id=" + this.report.testId;
|
||||
},
|
||||
isNotRunning() {
|
||||
return "Running" !== this.report.status;
|
||||
},
|
||||
projectId() {
|
||||
return getCurrentProjectID();
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.report-container .el-tabs__header {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.report-container {
|
||||
height: calc(100vh - 155px);
|
||||
min-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.report-header {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.report-header a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.report-header .time {
|
||||
color: #909399;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.report-container .fail {
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
.report-container .is-active .fail {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.report-console {
|
||||
height: calc(100vh - 270px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.export-button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.scenario-result .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.report-body{
|
||||
min-width: 750px!important;
|
||||
}
|
||||
</style>
|
|
@ -165,7 +165,7 @@
|
|||
<!-- 执行结果 -->
|
||||
<el-drawer :visible.sync="runVisible" :destroy-on-close="true" direction="ltr" :withHeader="true" :modal="false"
|
||||
size="90%">
|
||||
<micro-app :to="`/report/view/${reportId}`" service="ui"/>
|
||||
<micro-app :to="`/ui/report/view/${reportId}`" service="ui"/>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</el-card>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export {operationConfirm, removeGoBackListener, handleCtrlSEvent, byteToSize, getTypeByFileName, strMapToObj} from "metersphere-frontend/src/utils";
|
||||
export {operationConfirm, removeGoBackListener, handleCtrlSEvent, byteToSize, getTypeByFileName, strMapToObj, getUUID, windowPrint} from "metersphere-frontend/src/utils";
|
||||
export {parseCustomFilesForList, getCustomFieldFilter, buildBatchParam} from "metersphere-frontend/src/utils/tableUtils";
|
||||
export {getCurrentProjectID, getCurrentWorkspaceId, getCurrentUser} from "metersphere-frontend/src/utils/token";
|
||||
export {hasLicense, hasPermissions, hasPermission} from "metersphere-frontend/src/utils/permission";
|
||||
|
|
Loading…
Reference in New Issue