fix(测试跟踪): 测试计划报告导出性能测试报告无法打开
--bug=1018685 --user=陈建星 【测试跟踪】测试计划-测试报告-导出-接口和性能的报告无法打开 https://www.tapd.cn/55049933/s/1273189
This commit is contained in:
parent
31ddfb94fc
commit
df34484cc6
|
@ -1,768 +0,0 @@
|
||||||
<template>
|
|
||||||
<!-- <div>-->
|
|
||||||
<!-- <!– 基本配置 –>-->
|
|
||||||
<!-- <el-row>-->
|
|
||||||
<!-- <el-col :span="6">-->
|
|
||||||
<!-- <el-form :inline="true" :disabled="isReadOnly">-->
|
|
||||||
<!-- <el-form-item>-->
|
|
||||||
<!-- <div>{{ $t('load_test.connect_timeout') }}</div>-->
|
|
||||||
<!-- </el-form-item>-->
|
|
||||||
<!-- <el-form-item>-->
|
|
||||||
<!-- <el-input-number-->
|
|
||||||
<!-- size="mini" v-model="timeout"-->
|
|
||||||
<!-- controls-position="right"-->
|
|
||||||
<!-- :min="0"/>-->
|
|
||||||
<!-- </el-form-item>-->
|
|
||||||
<!-- <el-form-item>-->
|
|
||||||
<!-- ms-->
|
|
||||||
<!-- </el-form-item>-->
|
|
||||||
<!-- </el-form>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- <el-col :span="6">-->
|
|
||||||
<!-- <el-form :inline="true" :disabled="isReadOnly">-->
|
|
||||||
<!-- <el-form-item>-->
|
|
||||||
<!-- <div>{{ $t('load_test.response_timeout') }}</div>-->
|
|
||||||
<!-- </el-form-item>-->
|
|
||||||
<!-- <el-form-item>-->
|
|
||||||
<!-- <el-input-number-->
|
|
||||||
<!-- size="mini" :min="0"-->
|
|
||||||
<!-- controls-position="right"-->
|
|
||||||
<!-- v-model="responseTimeout"/>-->
|
|
||||||
<!-- </el-form-item>-->
|
|
||||||
<!-- <el-form-item>-->
|
|
||||||
<!-- ms-->
|
|
||||||
<!-- </el-form-item>-->
|
|
||||||
<!-- </el-form>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- <el-col :span="6">-->
|
|
||||||
<!-- <el-form :inline="true" :disabled="isReadOnly">-->
|
|
||||||
<!-- <el-form-item>-->
|
|
||||||
<!-- <div>-->
|
|
||||||
<!-- {{ $t('load_test.granularity') }}-->
|
|
||||||
<!-- <el-popover-->
|
|
||||||
<!-- placement="left"-->
|
|
||||||
<!-- width="300"-->
|
|
||||||
<!-- trigger="hover">-->
|
|
||||||
<!-- <el-table :data="granularityData">-->
|
|
||||||
<!-- <el-table-column property="start" :label="$t('load_test.duration')">-->
|
|
||||||
<!-- <template v-slot:default="scope">-->
|
|
||||||
<!-- <span>{{ scope.row.start }}S - {{ scope.row.end }}S</span>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column property="granularity" :label="$t('load_test.granularity')"/>-->
|
|
||||||
<!-- </el-table>-->
|
|
||||||
<!-- <i slot="reference" class="el-icon-info pointer"/>-->
|
|
||||||
<!-- </el-popover>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </el-form-item>-->
|
|
||||||
<!-- <el-form-item>-->
|
|
||||||
<!-- <el-select v-model="granularity" :placeholder="$t('commons.please_select')" size="mini"-->
|
|
||||||
<!-- clearable>-->
|
|
||||||
<!-- <el-option v-for="op in granularityData" :key="op.granularity" :label="op.granularity"-->
|
|
||||||
<!-- :value="op.granularity"></el-option>-->
|
|
||||||
<!-- </el-select>-->
|
|
||||||
<!-- </el-form-item>-->
|
|
||||||
<!-- </el-form>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- <el-col :span="6">-->
|
|
||||||
<!-- <el-form :inline="true" :disabled="isReadOnly">-->
|
|
||||||
<!-- <el-form-item>-->
|
|
||||||
<!-- <div>{{ $t('load_test.custom_http_code') }}</div>-->
|
|
||||||
<!-- </el-form-item>-->
|
|
||||||
<!-- <el-form-item>-->
|
|
||||||
<!-- <el-input-->
|
|
||||||
<!-- size="mini" v-model="statusCodeStr"-->
|
|
||||||
<!-- :placeholder="$t('load_test.separated_by_commas')"-->
|
|
||||||
<!-- @input="checkStatusCode"></el-input>-->
|
|
||||||
<!-- </el-form-item>-->
|
|
||||||
<!-- </el-form>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- </el-row>-->
|
|
||||||
|
|
||||||
<!-- <!– DNS –>-->
|
|
||||||
<!-- <el-row type="flex" justify="start">-->
|
|
||||||
<!-- <el-col :span="8">-->
|
|
||||||
<!-- <h3>{{ $t('load_test.domain_bind') }}</h3>-->
|
|
||||||
<!-- <el-button icon="el-icon-circle-plus-outline"-->
|
|
||||||
<!-- :disabled="isReadOnly"-->
|
|
||||||
<!-- plain size="mini" @click="add('domains')">-->
|
|
||||||
<!-- {{ $t('commons.add') }}-->
|
|
||||||
<!-- </el-button>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- </el-row>-->
|
|
||||||
<!-- <el-row>-->
|
|
||||||
<!-- <el-col :span="24">-->
|
|
||||||
<!-- <el-table :data="domains" size="mini" class="tb-edit" align="center" border highlight-current-row>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- :label="$t('load_test.domain')"-->
|
|
||||||
<!-- show-overflow-tooltip>-->
|
|
||||||
<!-- <template v-slot:default="{row}">-->
|
|
||||||
<!-- <el-input-->
|
|
||||||
<!-- size="mini"-->
|
|
||||||
<!-- v-if="!isReadOnly"-->
|
|
||||||
<!-- type="textarea"-->
|
|
||||||
<!-- :rows="1"-->
|
|
||||||
<!-- class="edit-input"-->
|
|
||||||
<!-- v-model="row.domain"-->
|
|
||||||
<!-- :placeholder="$t('load_test.domain')"-->
|
|
||||||
<!-- clearable>-->
|
|
||||||
<!-- </el-input>-->
|
|
||||||
<!-- <span>{{ row.domain }}</span>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- :label="$t('load_test.ip')"-->
|
|
||||||
<!-- show-overflow-tooltip>-->
|
|
||||||
<!-- <template v-slot:default="{row}">-->
|
|
||||||
<!-- <el-input-->
|
|
||||||
<!-- size="mini"-->
|
|
||||||
<!-- v-if="!isReadOnly"-->
|
|
||||||
<!-- type="textarea"-->
|
|
||||||
<!-- class="edit-input"-->
|
|
||||||
<!-- :rows="1"-->
|
|
||||||
<!-- v-model="row.ip"-->
|
|
||||||
<!-- :placeholder="$t('load_test.ip')"-->
|
|
||||||
<!-- clearable></el-input>-->
|
|
||||||
<!-- <span>{{ row.ip }}</span>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- :label="$t('load_test.enable')"-->
|
|
||||||
<!-- show-overflow-tooltip>-->
|
|
||||||
<!-- <template v-slot:default="{row}">-->
|
|
||||||
<!-- <el-switch-->
|
|
||||||
<!-- :disabled="isReadOnly"-->
|
|
||||||
<!-- size="mini"-->
|
|
||||||
<!-- v-model="row.enable"-->
|
|
||||||
<!-- inactive-color="#DCDFE6"-->
|
|
||||||
<!-- >-->
|
|
||||||
<!-- </el-switch>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column align="center" :label="$t('load_test.operating')">-->
|
|
||||||
<!-- <template v-slot:default="{row, $index}">-->
|
|
||||||
<!-- <ms-table-operator-button :tip="$t('commons.delete')" icon="el-icon-delete"-->
|
|
||||||
<!-- type="danger" :disabled="isReadOnly"-->
|
|
||||||
<!-- @exec="del(row, 'domains', $index)"/>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- </el-table>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- </el-row>-->
|
|
||||||
|
|
||||||
<!-- <!– csv 配置 –>-->
|
|
||||||
<!-- <el-row>-->
|
|
||||||
<!-- <el-col :span="8">-->
|
|
||||||
<!-- <h3>CSVDataSet</h3>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- </el-row>-->
|
|
||||||
<!-- <el-row>-->
|
|
||||||
<!-- <el-col :span="24">-->
|
|
||||||
<!-- <el-table :data="csvFiles" size="mini" class="tb-edit" align="center" border highlight-current-row>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- prop="name"-->
|
|
||||||
<!-- :label="$t('commons.name')">-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column align="center" prop="csvSplit" :label="$t('load_test.csv_split')">-->
|
|
||||||
<!-- <template v-slot:default="{row}">-->
|
|
||||||
<!-- <el-switch :disabled="isReadOnly" v-model="row.csvSplit"/>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column align="center" prop="csvHasHeader" :label="$t('load_test.csv_has_header')">-->
|
|
||||||
<!-- <template v-slot:default="{row}">-->
|
|
||||||
<!-- <el-switch :disabled="isReadOnly || !row.csvSplit" v-model="row.csvHasHeader"/>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- </el-table>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- </el-row>-->
|
|
||||||
|
|
||||||
<!-- <!– 参数列表 –>-->
|
|
||||||
<!-- <el-row>-->
|
|
||||||
<!-- <el-col :span="8">-->
|
|
||||||
<!-- <h3>{{ $t('load_test.params') }}</h3>-->
|
|
||||||
<!-- <el-button icon="el-icon-circle-plus-outline"-->
|
|
||||||
<!-- :disabled="isReadOnly"-->
|
|
||||||
<!-- plain size="mini" @click="add('params')">-->
|
|
||||||
<!-- {{ $t('commons.add') }}-->
|
|
||||||
<!-- </el-button>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- </el-row>-->
|
|
||||||
<!-- <el-row>-->
|
|
||||||
<!-- <el-col :span="24">-->
|
|
||||||
<!-- <el-table :data="params" size="mini" class="tb-edit" align="center" border highlight-current-row>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- :label="$t('load_test.param_name')"-->
|
|
||||||
<!-- show-overflow-tooltip>-->
|
|
||||||
<!-- <template v-slot:default="{row}">-->
|
|
||||||
<!-- <el-input-->
|
|
||||||
<!-- size="mini"-->
|
|
||||||
<!-- v-if="!isReadOnly"-->
|
|
||||||
<!-- type="textarea"-->
|
|
||||||
<!-- :rows="1"-->
|
|
||||||
<!-- class="edit-input"-->
|
|
||||||
<!-- v-model="row.name"-->
|
|
||||||
<!-- :placeholder="$t('load_test.param_name')"-->
|
|
||||||
<!-- clearable>-->
|
|
||||||
<!-- </el-input>-->
|
|
||||||
<!-- <span>{{ row.name }}</span>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- :label="$t('load_test.param_value')"-->
|
|
||||||
<!-- show-overflow-tooltip align="center">-->
|
|
||||||
<!-- <template v-slot:default="{row}">-->
|
|
||||||
<!-- <el-input-->
|
|
||||||
<!-- size="mini"-->
|
|
||||||
<!-- v-if="!isReadOnly"-->
|
|
||||||
<!-- type="textarea"-->
|
|
||||||
<!-- class="edit-input"-->
|
|
||||||
<!-- :rows="1"-->
|
|
||||||
<!-- v-model="row.value"-->
|
|
||||||
<!-- :placeholder="$t('load_test.param_value')"-->
|
|
||||||
<!-- clearable></el-input>-->
|
|
||||||
<!-- <span>{{ row.value }}</span>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- :label="$t('load_test.enable')"-->
|
|
||||||
<!-- show-overflow-tooltip>-->
|
|
||||||
<!-- <template v-slot:default="{row}">-->
|
|
||||||
<!-- <el-switch-->
|
|
||||||
<!-- :disabled="isReadOnly"-->
|
|
||||||
<!-- size="mini"-->
|
|
||||||
<!-- v-model="row.enable"-->
|
|
||||||
<!-- inactive-color="#DCDFE6">-->
|
|
||||||
<!-- </el-switch>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column align="center" :label="$t('load_test.operating')">-->
|
|
||||||
<!-- <template v-slot:default="{row, $index}">-->
|
|
||||||
<!-- <ms-table-operator-button :tip="$t('commons.delete')" icon="el-icon-delete"-->
|
|
||||||
<!-- type="danger"-->
|
|
||||||
<!-- :disabled="isReadOnly"-->
|
|
||||||
<!-- @exec="del(row, 'params', $index)"/>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- </el-table>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- </el-row>-->
|
|
||||||
|
|
||||||
<!-- <!– JMeter Properties –>-->
|
|
||||||
<!-- <el-row>-->
|
|
||||||
<!-- <el-col :span="8">-->
|
|
||||||
<!-- <h3>JMeter Properties</h3>-->
|
|
||||||
<!-- <el-button icon="el-icon-circle-plus-outline"-->
|
|
||||||
<!-- :disabled="isReadOnly"-->
|
|
||||||
<!-- plain size="mini" @click="add('properties')">-->
|
|
||||||
<!-- {{ $t('commons.add') }}-->
|
|
||||||
<!-- </el-button>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- </el-row>-->
|
|
||||||
<!-- <el-row>-->
|
|
||||||
<!-- <el-col :span="24">-->
|
|
||||||
<!-- <el-table :data="properties" size="mini" class="tb-edit" align="center" border highlight-current-row>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- :label="$t('load_test.param_name')"-->
|
|
||||||
<!-- show-overflow-tooltip>-->
|
|
||||||
<!-- <template v-slot:default="{row}">-->
|
|
||||||
<!-- <el-input-->
|
|
||||||
<!-- size="mini"-->
|
|
||||||
<!-- v-if="!isReadOnly"-->
|
|
||||||
<!-- type="textarea"-->
|
|
||||||
<!-- :rows="1"-->
|
|
||||||
<!-- class="edit-input"-->
|
|
||||||
<!-- v-model="row.name"-->
|
|
||||||
<!-- :placeholder="$t('load_test.param_name')"-->
|
|
||||||
<!-- clearable>-->
|
|
||||||
<!-- </el-input>-->
|
|
||||||
<!-- <span>{{ row.name }}</span>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- :label="$t('load_test.param_value')"-->
|
|
||||||
<!-- show-overflow-tooltip align="center">-->
|
|
||||||
<!-- <template v-slot:default="{row}">-->
|
|
||||||
<!-- <el-input-->
|
|
||||||
<!-- size="mini"-->
|
|
||||||
<!-- v-if="!isReadOnly"-->
|
|
||||||
<!-- type="textarea"-->
|
|
||||||
<!-- class="edit-input"-->
|
|
||||||
<!-- :rows="1"-->
|
|
||||||
<!-- v-model="row.value"-->
|
|
||||||
<!-- :placeholder="$t('load_test.param_value')"-->
|
|
||||||
<!-- clearable></el-input>-->
|
|
||||||
<!-- <span>{{ row.value }}</span>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- :label="$t('load_test.enable')"-->
|
|
||||||
<!-- show-overflow-tooltip>-->
|
|
||||||
<!-- <template v-slot:default="{row}">-->
|
|
||||||
<!-- <el-switch-->
|
|
||||||
<!-- :disabled="isReadOnly"-->
|
|
||||||
<!-- size="mini"-->
|
|
||||||
<!-- v-model="row.enable"-->
|
|
||||||
<!-- inactive-color="#DCDFE6">-->
|
|
||||||
<!-- </el-switch>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column align="center" :label="$t('load_test.operating')">-->
|
|
||||||
<!-- <template v-slot:default="{row, $index}">-->
|
|
||||||
<!-- <ms-table-operator-button :tip="$t('commons.delete')" icon="el-icon-delete"-->
|
|
||||||
<!-- type="danger"-->
|
|
||||||
<!-- :disabled="isReadOnly"-->
|
|
||||||
<!-- @exec="del(row, 'properties', $index)"/>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- </el-table>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- </el-row>-->
|
|
||||||
|
|
||||||
<!-- <!– System Properties –>-->
|
|
||||||
<!-- <el-row>-->
|
|
||||||
<!-- <el-col :span="8">-->
|
|
||||||
<!-- <h3>System Properties</h3>-->
|
|
||||||
<!-- <el-button icon="el-icon-circle-plus-outline"-->
|
|
||||||
<!-- :disabled="isReadOnly"-->
|
|
||||||
<!-- plain size="mini" @click="add('systemProperties')">-->
|
|
||||||
<!-- {{ $t('commons.add') }}-->
|
|
||||||
<!-- </el-button>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- </el-row>-->
|
|
||||||
<!-- <el-row>-->
|
|
||||||
<!-- <el-col :span="24">-->
|
|
||||||
<!-- <el-table :data="systemProperties" size="mini" class="tb-edit" align="center" border highlight-current-row>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- :label="$t('load_test.param_name')"-->
|
|
||||||
<!-- show-overflow-tooltip>-->
|
|
||||||
<!-- <template v-slot:default="{row}">-->
|
|
||||||
<!-- <el-input-->
|
|
||||||
<!-- size="mini"-->
|
|
||||||
<!-- v-if="!isReadOnly"-->
|
|
||||||
<!-- type="textarea"-->
|
|
||||||
<!-- :rows="1"-->
|
|
||||||
<!-- class="edit-input"-->
|
|
||||||
<!-- v-model="row.name"-->
|
|
||||||
<!-- :placeholder="$t('load_test.param_name')"-->
|
|
||||||
<!-- clearable>-->
|
|
||||||
<!-- </el-input>-->
|
|
||||||
<!-- <span>{{ row.name }}</span>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- :label="$t('load_test.param_value')"-->
|
|
||||||
<!-- show-overflow-tooltip align="center">-->
|
|
||||||
<!-- <template v-slot:default="{row}">-->
|
|
||||||
<!-- <el-input-->
|
|
||||||
<!-- size="mini"-->
|
|
||||||
<!-- v-if="!isReadOnly"-->
|
|
||||||
<!-- type="textarea"-->
|
|
||||||
<!-- class="edit-input"-->
|
|
||||||
<!-- :rows="1"-->
|
|
||||||
<!-- v-model="row.value"-->
|
|
||||||
<!-- :placeholder="$t('load_test.param_value')"-->
|
|
||||||
<!-- clearable></el-input>-->
|
|
||||||
<!-- <span>{{ row.value }}</span>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- :label="$t('load_test.enable')"-->
|
|
||||||
<!-- show-overflow-tooltip>-->
|
|
||||||
<!-- <template v-slot:default="{row}">-->
|
|
||||||
<!-- <el-switch-->
|
|
||||||
<!-- :disabled="isReadOnly"-->
|
|
||||||
<!-- size="mini"-->
|
|
||||||
<!-- v-model="row.enable"-->
|
|
||||||
<!-- inactive-color="#DCDFE6">-->
|
|
||||||
<!-- </el-switch>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column align="center" :label="$t('load_test.operating')">-->
|
|
||||||
<!-- <template v-slot:default="{row, $index}">-->
|
|
||||||
<!-- <ms-table-operator-button :tip="$t('commons.delete')" icon="el-icon-delete"-->
|
|
||||||
<!-- type="danger"-->
|
|
||||||
<!-- :disabled="isReadOnly"-->
|
|
||||||
<!-- @exec="del(row, 'systemProperties', $index)"/>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- </el-table>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- </el-row>-->
|
|
||||||
|
|
||||||
<!-- <!– 监控配置 –>-->
|
|
||||||
<!-- <el-row>-->
|
|
||||||
<!-- <el-col :span="8">-->
|
|
||||||
<!-- <h3>{{ $t('commons.monitor') }}</h3>-->
|
|
||||||
<!-- <el-button icon="el-icon-circle-plus-outline" :disabled="isReadOnly"-->
|
|
||||||
<!-- plain size="mini" @click="addMonitor">-->
|
|
||||||
<!-- {{ $t('commons.add') }}-->
|
|
||||||
<!-- </el-button>-->
|
|
||||||
<!-- <el-button icon="el-icon-circle-plus-outline" plain size="mini"-->
|
|
||||||
<!-- :disabled="isReadOnly"-->
|
|
||||||
<!-- @click="batchAddMonitor">-->
|
|
||||||
<!-- {{ $t('commons.batch_add') }}-->
|
|
||||||
<!-- </el-button>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- </el-row>-->
|
|
||||||
<!-- <el-row>-->
|
|
||||||
<!-- <el-col :span="24">-->
|
|
||||||
<!-- <el-table :data="monitorParams" size="mini" class="tb-edit" border highlight-current-row>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- prop="name"-->
|
|
||||||
<!-- :label="$t('commons.name')">-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- prop="ip"-->
|
|
||||||
<!-- label="IP">-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- prop="port"-->
|
|
||||||
<!-- label="Port">-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- prop="description"-->
|
|
||||||
<!-- :label="$t('commons.description')">-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- <el-table-column align="center" :label="$t('load_test.operating')">-->
|
|
||||||
<!-- <template v-slot:default="{row, $index}">-->
|
|
||||||
<!-- <ms-table-operator-button tip="编辑" icon="el-icon-edit"-->
|
|
||||||
<!-- type="primary"-->
|
|
||||||
<!-- :disabled="isReadOnly"-->
|
|
||||||
<!-- @exec="modifyMonitor(row, $index)"/>-->
|
|
||||||
<!-- <ms-table-operator-button :tip="$t('commons.delete')" icon="el-icon-delete"-->
|
|
||||||
<!-- :disabled="isReadOnly"-->
|
|
||||||
<!-- type="danger"-->
|
|
||||||
<!-- @exec="delMonitor(row, $index)"/>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </el-table-column>-->
|
|
||||||
<!-- </el-table>-->
|
|
||||||
<!-- </el-col>-->
|
|
||||||
<!-- </el-row>-->
|
|
||||||
|
|
||||||
<!-- <edit-monitor ref="monitorDialog" :testId="testId" :list.sync="monitorParams"/>-->
|
|
||||||
<!-- <batch-add-monitor ref="batchMonitorDialog" @batchSave="batchSave"/>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import MsTableOperatorButton from "metersphere-frontend/src/components/MsTableOperatorButton.vue";
|
|
||||||
// import EditMonitor from "./EditMonitor";
|
|
||||||
// import BatchAddMonitor from "./BatchAddMonitor";
|
|
||||||
// import {getAdvancedConfig} from "@/api/performance";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "PerformanceAdvancedConfig",
|
|
||||||
components: {
|
|
||||||
// BatchAddMonitor,
|
|
||||||
// EditMonitor,
|
|
||||||
MsTableOperatorButton},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
timeout: undefined,
|
|
||||||
responseTimeout: undefined,
|
|
||||||
statusCode: [],
|
|
||||||
domains: [],
|
|
||||||
params: [],
|
|
||||||
properties: [],
|
|
||||||
systemProperties: [],
|
|
||||||
monitorParams: [],
|
|
||||||
csvFiles: [],
|
|
||||||
csvConfig: [],
|
|
||||||
statusCodeStr: '',
|
|
||||||
granularity: undefined,
|
|
||||||
granularityData: [
|
|
||||||
{start: 0, end: 100, granularity: 1},
|
|
||||||
{start: 101, end: 500, granularity: 5},
|
|
||||||
{start: 501, end: 1000, granularity: 10},
|
|
||||||
{start: 1001, end: 3000, granularity: 30},
|
|
||||||
{start: 3001, end: 6000, granularity: 60},
|
|
||||||
{start: 6001, end: 30000, granularity: 300},
|
|
||||||
{start: 30001, end: 60000, granularity: 600},
|
|
||||||
{start: 60001, end: 180000, granularity: 1800},
|
|
||||||
{start: 180001, end: 360000, granularity: 3600},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
testId: String,
|
|
||||||
reportId: {
|
|
||||||
type: String
|
|
||||||
},
|
|
||||||
isReadOnly: {
|
|
||||||
type: Boolean,
|
|
||||||
default() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isShare: Boolean,
|
|
||||||
shareId: String,
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
if (this.testId) {
|
|
||||||
this.getAdvancedConfig();
|
|
||||||
} else if (this.reportId) {
|
|
||||||
this.getAdvancedConfig('report');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
testId() {
|
|
||||||
if (this.testId) {
|
|
||||||
this.getAdvancedConfig();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
csvFiles() {
|
|
||||||
this.refreshCsv();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getAdvancedConfig(type) {
|
|
||||||
getAdvancedConfig(type, this.testId, this.reportId, this.isShare, this.shareId)
|
|
||||||
.then(response => {
|
|
||||||
let data = JSON.parse(response.data);
|
|
||||||
this.timeout = data.timeout;
|
|
||||||
this.responseTimeout = data.responseTimeout;
|
|
||||||
this.statusCode = data.statusCode || [];
|
|
||||||
this.statusCodeStr = this.statusCode.join(',');
|
|
||||||
this.domains = data.domains || [];
|
|
||||||
this.params = data.params || [];
|
|
||||||
this.granularity = data.granularity;
|
|
||||||
this.monitorParams = data.monitorParams || [];
|
|
||||||
this.properties = data.properties || [];
|
|
||||||
this.systemProperties = data.systemProperties || [];
|
|
||||||
this.csvConfig = data.csvConfig;
|
|
||||||
this.refreshCsv();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
refreshCsv() {
|
|
||||||
if (this.csvConfig && this.csvFiles) {
|
|
||||||
this.csvFiles.forEach(f => {
|
|
||||||
f.csvSplit = this.csvConfig[f.name]?.csvSplit;
|
|
||||||
f.csvHasHeader = this.csvConfig[f.name]?.csvHasHeader;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
add(dataName) {
|
|
||||||
if (dataName === 'domains') {
|
|
||||||
this[dataName].push({
|
|
||||||
domain: 'fit2cloud.com',
|
|
||||||
enable: true,
|
|
||||||
ip: '127.0.0.1',
|
|
||||||
edit: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (dataName === 'params') {
|
|
||||||
this[dataName].push({
|
|
||||||
name: 'param1',
|
|
||||||
enable: true,
|
|
||||||
value: '0',
|
|
||||||
edit: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (dataName === 'properties') {
|
|
||||||
this[dataName].push({
|
|
||||||
name: 'prop1',
|
|
||||||
enable: true,
|
|
||||||
value: '0',
|
|
||||||
edit: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (dataName === 'systemProperties') {
|
|
||||||
this[dataName].push({
|
|
||||||
name: 'prop1',
|
|
||||||
enable: true,
|
|
||||||
value: '0',
|
|
||||||
edit: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
edit(row) {
|
|
||||||
row.edit = !row.edit;
|
|
||||||
},
|
|
||||||
del(row, dataName, index) {
|
|
||||||
this[dataName].splice(index, 1);
|
|
||||||
},
|
|
||||||
confirmEdit(row) {
|
|
||||||
row.edit = false;
|
|
||||||
row.enable = true;
|
|
||||||
},
|
|
||||||
groupBy(data, key) {
|
|
||||||
// return data.reduce((p, c) => {
|
|
||||||
// let name = c[key];
|
|
||||||
// if (!Object.prototype.hasOwnProperty.call(p, name)) {
|
|
||||||
// p[name] = 0;
|
|
||||||
// }
|
|
||||||
// p[name]++;
|
|
||||||
// return p;
|
|
||||||
// }, {});
|
|
||||||
},
|
|
||||||
validConfig() {
|
|
||||||
let counts = this.groupBy(this.domains, 'domain');
|
|
||||||
for (let c in counts) {
|
|
||||||
if (counts[c] > 1) {
|
|
||||||
this.$error(this.$t('load_test.domain_is_duplicate'));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
counts = this.groupBy(this.params, 'name');
|
|
||||||
for (let c in counts) {
|
|
||||||
if (counts[c] > 1) {
|
|
||||||
this.$error(this.$t('load_test.param_is_duplicate'));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
counts = this.groupBy(this.properties, 'name');
|
|
||||||
for (let c in counts) {
|
|
||||||
if (counts[c] > 1) {
|
|
||||||
this.$error(this.$t('load_test.param_is_duplicate'));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.domains.filter(d => !d.domain || !d.ip).length > 0) {
|
|
||||||
this.$error(this.$t('load_test.domain_ip_is_empty'));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this.params.filter(d => !d.name || !d.value).length > 0) {
|
|
||||||
this.$error(this.$t('load_test.param_name_value_is_empty'));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
checkStatusCode() {
|
|
||||||
let license_num = this.statusCodeStr;
|
|
||||||
license_num = license_num.replace(/[^\d,]/g, ''); // 清除“数字”和“.”以外的字符
|
|
||||||
this.statusCodeStr = license_num;
|
|
||||||
},
|
|
||||||
cancelAllEdit() {
|
|
||||||
this.domains.forEach(d => d.edit = false);
|
|
||||||
this.params.forEach(d => d.edit = false);
|
|
||||||
},
|
|
||||||
configurations() {
|
|
||||||
let statusCode = [];
|
|
||||||
if (this.statusCodeStr) {
|
|
||||||
statusCode = this.statusCodeStr.split(',');
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
timeout: this.timeout,
|
|
||||||
responseTimeout: this.responseTimeout,
|
|
||||||
statusCode: statusCode,
|
|
||||||
params: this.params,
|
|
||||||
properties: this.properties,
|
|
||||||
systemProperties: this.systemProperties,
|
|
||||||
csvConfig: this.csvFiles.reduce((result, curr) => {
|
|
||||||
result[curr.name] = {csvHasHeader: curr.csvHasHeader, csvSplit: curr.csvSplit};
|
|
||||||
return result;
|
|
||||||
}, {}),
|
|
||||||
domains: this.domains,
|
|
||||||
granularity: this.granularity,
|
|
||||||
monitorParams: this.monitorParams
|
|
||||||
};
|
|
||||||
},
|
|
||||||
addMonitor() {
|
|
||||||
this.$refs.monitorDialog.open();
|
|
||||||
},
|
|
||||||
batchAddMonitor() {
|
|
||||||
this.$refs.batchMonitorDialog.open();
|
|
||||||
},
|
|
||||||
batchSave(params) {
|
|
||||||
let targets = this._handleBatchVars(params);
|
|
||||||
targets.forEach(row => {
|
|
||||||
this.monitorParams.push(row);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
_handleBatchVars(data) {
|
|
||||||
let params = data.split("\n");
|
|
||||||
let keyValues = [];
|
|
||||||
params.forEach(item => {
|
|
||||||
let line = item.split(/,|,/);
|
|
||||||
if (line.length < 3) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let ipRe = new RegExp("^[0-9a-zA-Z,\.]*$");
|
|
||||||
if (!ipRe.test(line[1])) {
|
|
||||||
this.$message.warning("ip" + this.$t("commons.formatErr"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let numRe = new RegExp("^[0-9]*$");
|
|
||||||
if (!numRe.test(line[2])) {
|
|
||||||
this.$message.warning("Port" + this.$t("commons.type_of_num"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
keyValues.push({
|
|
||||||
name: line[0],
|
|
||||||
ip: line[1],
|
|
||||||
port: line[2],
|
|
||||||
description: line[3] || '',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return keyValues;
|
|
||||||
},
|
|
||||||
modifyMonitor(row, index) {
|
|
||||||
this.$refs.monitorDialog.open(row, index);
|
|
||||||
},
|
|
||||||
delMonitor(row, index) {
|
|
||||||
this.monitorParams.splice(index, 1);
|
|
||||||
},
|
|
||||||
refreshStatus() {
|
|
||||||
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.el-row {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-input {
|
|
||||||
padding-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tb-edit .el-textarea {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tb-edit .current-row .el-textarea {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tb-edit .current-row .el-textarea + span {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-col {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-col .el-table {
|
|
||||||
align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pointer {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.duration-input .el-input-number--mini {
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-select--mini {
|
|
||||||
width: 130px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,388 +0,0 @@
|
||||||
<template>
|
|
||||||
<div v-loading="result.loading">
|
|
||||||
<el-row type="flex" justify="space-between" align="middle">
|
|
||||||
<h4>{{ $t('load_test.scenario_list') }}</h4>
|
|
||||||
</el-row>
|
|
||||||
<el-row type="flex" justify="start" align="middle">
|
|
||||||
<ms-table-button icon="el-icon-circle-plus-outline"
|
|
||||||
:disabled="isReadOnly"
|
|
||||||
:content="$t('load_test.load_exist_jmx')" @click="loadJMX()"/>
|
|
||||||
<ms-table-button icon="el-icon-share"
|
|
||||||
:disabled="isReadOnly"
|
|
||||||
@click="loadApiAutomation()"
|
|
||||||
:content="$t('load_test.load_api_automation_jmx')"/>
|
|
||||||
</el-row>
|
|
||||||
<el-table class="basic-config" :data="threadGroups.filter(tg=>tg.deleted=='false')">
|
|
||||||
<el-table-column
|
|
||||||
:label="$t('load_test.scenario_name')">
|
|
||||||
<template v-slot:default="{row}">
|
|
||||||
{{ row.attributes.testname }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Enable/Disable">
|
|
||||||
<template v-slot:default="{row}">
|
|
||||||
<el-switch v-model="row.enabled"
|
|
||||||
inactive-color="#DCDFE6"
|
|
||||||
active-value="true"
|
|
||||||
inactive-value="false"
|
|
||||||
:disabled="isReadOnly || threadGroupDisable(row)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="CSVDataSet">
|
|
||||||
<template v-slot:default="scope">
|
|
||||||
<ms-tag v-for="(f, index) in scope.row.csvFiles"
|
|
||||||
:key="index"
|
|
||||||
effect="light"
|
|
||||||
:content="f"/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
:label="$t('load_test.thread_group')">
|
|
||||||
<template v-slot:default="{row}">
|
|
||||||
<span v-if="row.tgType === 'PostThreadGroup' || row.tgType === 'SetupThreadGroup'">
|
|
||||||
{{ row.tgType }}
|
|
||||||
</span>
|
|
||||||
<el-select v-else v-model="row.tgType"
|
|
||||||
:disabled="isReadOnly"
|
|
||||||
:placeholder="$t('commons.please_select')" size="small"
|
|
||||||
@change="tgTypeChange(row)">
|
|
||||||
<el-option v-for="tg in threadGroupForSelect" :key="tg.tagName" :label="tg.name"
|
|
||||||
:value="tg.testclass"></el-option>
|
|
||||||
</el-select>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
:label="$t('commons.operating')">
|
|
||||||
<template v-slot:default="{row}">
|
|
||||||
<el-button :disabled="isReadOnly || threadGroupDisable(row)"
|
|
||||||
@click="handleDeleteThreadGroup(row)"
|
|
||||||
type="danger"
|
|
||||||
icon="el-icon-delete" size="mini"
|
|
||||||
circle/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<el-row type="flex" justify="space-between" align="middle">
|
|
||||||
<h4>{{ $t('load_test.other_resource') }}</h4>
|
|
||||||
</el-row>
|
|
||||||
<el-row type="flex" justify="start" align="middle">
|
|
||||||
|
|
||||||
<ms-table-button icon="el-icon-circle-plus-outline"
|
|
||||||
:disabled="isReadOnly"
|
|
||||||
:content="$t('load_test.load_exist_file')" @click="loadFile()"/>
|
|
||||||
</el-row>
|
|
||||||
<el-table class="basic-config" :data="tableData">
|
|
||||||
<el-table-column
|
|
||||||
prop="name"
|
|
||||||
:label="$t('load_test.file_name')">
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
prop="size"
|
|
||||||
:label="$t('load_test.file_size')">
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
prop="type"
|
|
||||||
:label="$t('load_test.file_type')">
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
:label="$t('load_test.last_modify_time')">
|
|
||||||
<template v-slot:default="scope">
|
|
||||||
<i v-if="scope.row.updateTime > 0" class="el-icon-time"/>
|
|
||||||
<span class="last-modified">{{ scope.row.updateTime | datetimeFormat }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
:label="$t('commons.operating')">
|
|
||||||
<template v-slot:default="scope">
|
|
||||||
<el-button @click="handleDownload(scope.row)" :disabled="!scope.row.id || isReadOnly" type="primary"
|
|
||||||
icon="el-icon-download"
|
|
||||||
size="mini" circle/>
|
|
||||||
<el-button :disabled="isReadOnly" @click="handleDelete(scope.row, scope.$index)" type="danger"
|
|
||||||
icon="el-icon-delete" size="mini"
|
|
||||||
circle/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<exist-files ref="existFiles"
|
|
||||||
@fileChange="fileChange"
|
|
||||||
:file-list="fileList"
|
|
||||||
:table-data="tableData"
|
|
||||||
:upload-list="uploadList"
|
|
||||||
:is-read-only="isReadOnly"
|
|
||||||
:scenarios="threadGroups"/>
|
|
||||||
|
|
||||||
<exist-scenarios ref="existScenarios"
|
|
||||||
@fileChange="fileChange"
|
|
||||||
:file-list="fileList"
|
|
||||||
:table-data="tableData"
|
|
||||||
:upload-list="uploadList"
|
|
||||||
:scenarios="threadGroups"/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {Message} from "element-ui";
|
|
||||||
import MsTableButton from "metersphere-frontend/src/components/MsTableButton";
|
|
||||||
import MsTablePagination from "metersphere-frontend/src/components/pagination/TablePagination";
|
|
||||||
import MsTableOperatorButton from "metersphere-frontend/src/components/MsTableOperatorButton";
|
|
||||||
import MsDialogFooter from "metersphere-frontend/src/components/MsDialogFooter";
|
|
||||||
import ExistFiles from "./ExistFiles";
|
|
||||||
import ExistScenarios from "./ExistScenarios";
|
|
||||||
// import {findThreadGroup} from "@/business/api/model/ThreadGroup";
|
|
||||||
import {hasPermission} from "metersphere-frontend/src/utils/permission";
|
|
||||||
import MsTag from "metersphere-frontend/src/components/MsTag";
|
|
||||||
import {downloadFile, getFiles, getMetadataById} from "@/api/performance";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "PerformanceBasicConfig",
|
|
||||||
components: {
|
|
||||||
MsTag,
|
|
||||||
ExistScenarios, ExistFiles, MsDialogFooter, MsTableOperatorButton, MsTablePagination, MsTableButton
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
test: {
|
|
||||||
type: Object
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
result: {},
|
|
||||||
isReadOnly: false,
|
|
||||||
projectLoadingResult: {},
|
|
||||||
getFileMetadataPath: "/performance/file/metadata",
|
|
||||||
getFileMetadataById: "/performance/file/getMetadataById",
|
|
||||||
jmxDownloadPath: '/performance/file/download',
|
|
||||||
jmxDeletePath: '/performance/file/delete',
|
|
||||||
fileList: [],
|
|
||||||
tableData: [],
|
|
||||||
uploadList: [],
|
|
||||||
metadataIdList: [],
|
|
||||||
fileNumLimit: 10,
|
|
||||||
threadGroups: [],
|
|
||||||
loadFileVisible: false,
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: 5,
|
|
||||||
total: 0,
|
|
||||||
existFiles: [],
|
|
||||||
apiScenarios: [],
|
|
||||||
loadApiAutomationVisible: false,
|
|
||||||
selectIds: new Set(),
|
|
||||||
threadGroupForSelect: [
|
|
||||||
{
|
|
||||||
name: 'ThreadGroup',
|
|
||||||
tagName: 'ThreadGroup',
|
|
||||||
testclass: 'ThreadGroup',
|
|
||||||
guiclass: 'ThreadGroupGui'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ConcurrencyThreadGroup',
|
|
||||||
tagName: 'com.blazemeter.jmeter.threads.concurrency.ConcurrencyThreadGroup',
|
|
||||||
testclass: 'com.blazemeter.jmeter.threads.concurrency.ConcurrencyThreadGroup',
|
|
||||||
guiclass: "com.blazemeter.jmeter.threads.concurrency.ConcurrencyThreadGroupGui"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (this.test.id) {
|
|
||||||
this.getFileMetadata(this.test);
|
|
||||||
}
|
|
||||||
this.isReadOnly = !hasPermission('PROJECT_PERFORMANCE_TEST:READ+EDIT');
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
test() {
|
|
||||||
if (this.test.id) {
|
|
||||||
this.getFileMetadata(this.test);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getFileMetadata(test) {
|
|
||||||
this.fileList = [];
|
|
||||||
this.tableData = [];
|
|
||||||
this.uploadList = [];
|
|
||||||
this.metadataIdList = [];
|
|
||||||
getFiles(test.id)
|
|
||||||
.then(response => {
|
|
||||||
let files = response.data;
|
|
||||||
if (!files) {
|
|
||||||
Message.error({message: this.$t('load_test.related_file_not_found'), showClose: true});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// deep copy
|
|
||||||
this.fileList = JSON.parse(JSON.stringify(files));
|
|
||||||
this.tableData = JSON.parse(JSON.stringify(files));
|
|
||||||
this.tableData.map(f => {
|
|
||||||
f.size = (f.size / 1024).toFixed(2) + ' KB';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
selectAttachFileById(metadataIdArr) {
|
|
||||||
this.metadataIdList = metadataIdArr;
|
|
||||||
for (let i = 0; i < metadataIdArr.length; i++) {
|
|
||||||
let id = metadataIdArr[i];
|
|
||||||
getMetadataById(id)
|
|
||||||
.then(response => {
|
|
||||||
let files = response.data;
|
|
||||||
if (files) {
|
|
||||||
this.fileList.push(JSON.parse(JSON.stringify(files)));
|
|
||||||
this.tableData.push(JSON.parse(JSON.stringify(files)));
|
|
||||||
this.tableData.map(f => {
|
|
||||||
f.size = (f.size / 1024).toFixed(2) + ' KB';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleDownload(file) {
|
|
||||||
downloadFile(this.jmxDownloadPath, file)
|
|
||||||
.then(response => {
|
|
||||||
const content = response.data;
|
|
||||||
const blob = new Blob([content]);
|
|
||||||
if ("download" in document.createElement("a")) {
|
|
||||||
// 非IE下载
|
|
||||||
// chrome/firefox
|
|
||||||
let aTag = document.createElement('a');
|
|
||||||
aTag.download = file.name;
|
|
||||||
aTag.href = URL.createObjectURL(blob);
|
|
||||||
aTag.click();
|
|
||||||
URL.revokeObjectURL(aTag.href);
|
|
||||||
} else {
|
|
||||||
// IE10+下载
|
|
||||||
navigator.msSaveBlob(blob, this.filename);
|
|
||||||
}
|
|
||||||
}).catch(e => {
|
|
||||||
Message.error({message: e.message, showClose: true});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleDelete(file) {
|
|
||||||
this.$alert(this.$t('load_test.delete_file_confirm') + file.name + "?", '', {
|
|
||||||
confirmButtonText: this.$t('commons.confirm'),
|
|
||||||
callback: (action) => {
|
|
||||||
if (action === 'confirm') {
|
|
||||||
this._handleDelete(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
_handleDelete(file) {
|
|
||||||
let index = this.fileList.findIndex(f => f.name === file.name);
|
|
||||||
if (index > -1) {
|
|
||||||
this.fileList.splice(index, 1);
|
|
||||||
}
|
|
||||||
index = this.tableData.findIndex(f => f.name === file.name);
|
|
||||||
if (index > -1) {
|
|
||||||
this.tableData.splice(index, 1);
|
|
||||||
}
|
|
||||||
//
|
|
||||||
let i = this.uploadList.findIndex(upLoadFile => upLoadFile.name === file.name);
|
|
||||||
if (i > -1) {
|
|
||||||
this.uploadList.splice(i, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let jmxIndex = this.threadGroups.findIndex(tg => tg.handler === file.name);
|
|
||||||
while (jmxIndex !== -1) {
|
|
||||||
this.threadGroups.splice(jmxIndex, 1);
|
|
||||||
jmxIndex = this.threadGroups.findIndex(tg => tg.handler === file.name);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleDeleteThreadGroup(tg) {
|
|
||||||
this.$alert(this.$t('load_test.delete_threadgroup_confirm') + tg.attributes.testname + "?", '', {
|
|
||||||
confirmButtonText: this.$t('commons.confirm'),
|
|
||||||
callback: (action) => {
|
|
||||||
if (action === 'confirm') {
|
|
||||||
tg.deleted = 'true';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
threadGroupDisable(row) {
|
|
||||||
return this.threadGroups.filter(tg => tg.enabled == 'true').length === 1 && row.enabled == 'true';
|
|
||||||
},
|
|
||||||
tgTypeChange(row) {
|
|
||||||
this.$emit("tgTypeChange", row);
|
|
||||||
},
|
|
||||||
updatedFileList() {
|
|
||||||
return this.fileList;// 表示修改了已经上传的文件列表
|
|
||||||
},
|
|
||||||
conversionMetadataIdList() {
|
|
||||||
return this.metadataIdList;// 表示修改了已经上传的文件列表
|
|
||||||
},
|
|
||||||
fileSorts() {
|
|
||||||
let fileSorts = {};
|
|
||||||
this.tableData.forEach((f, index) => {
|
|
||||||
fileSorts[f.name] = index;
|
|
||||||
});
|
|
||||||
return fileSorts;
|
|
||||||
},
|
|
||||||
loadJMX() {
|
|
||||||
this.$refs.existFiles.open('jmx');
|
|
||||||
},
|
|
||||||
loadFile() {
|
|
||||||
this.$refs.existFiles.open('resource');
|
|
||||||
},
|
|
||||||
loadApiAutomation() {
|
|
||||||
this.$refs.existScenarios.open();
|
|
||||||
},
|
|
||||||
fileChange(threadGroups) {
|
|
||||||
this.$emit('fileChange', threadGroups);
|
|
||||||
},
|
|
||||||
validConfig() {
|
|
||||||
if (this.uploadList.length + this.fileList.length > this.fileNumLimit) {
|
|
||||||
this.$refs.existFiles.handleExceed();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.threadGroups.filter(tg => tg.enabled == 'true').length === 0) {
|
|
||||||
this.$error(this.$t('load_test.threadgroup_at_least_one'));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
importScenario(scenarioId) {
|
|
||||||
this.$refs.existScenarios.selectIds.add(scenarioId);
|
|
||||||
},
|
|
||||||
importCase(caseObj) {
|
|
||||||
let suffixIndex = caseObj.name.lastIndexOf(".jmx");
|
|
||||||
let jmxName = caseObj.name.substring(0, suffixIndex) + "_" + new Date().getTime() + ".jmx";
|
|
||||||
let threadGroups = findThreadGroup(caseObj.xml, jmxName);
|
|
||||||
threadGroups.forEach(tg => {
|
|
||||||
tg.options = {};
|
|
||||||
});
|
|
||||||
this.fileChange(threadGroups);
|
|
||||||
let file = new File([caseObj.xml], jmxName);
|
|
||||||
this.uploadList.push(file);
|
|
||||||
this.tableData.push({
|
|
||||||
name: file.name,
|
|
||||||
size: (file.size / 1024).toFixed(2) + ' KB',
|
|
||||||
type: 'JMX',
|
|
||||||
updateTime: file.lastModified,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleUpload() {
|
|
||||||
// 从api创建的测试
|
|
||||||
this.$refs.existScenarios.handleImport();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.basic-config {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-modified {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-dialog :deep( .el-dialog__body ) {
|
|
||||||
padding: 10px 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -246,8 +246,8 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MsChart from "metersphere-frontend/src/components/chart/MsChart";
|
import MsChart from "metersphere-frontend/src/components/chart/MsChart";
|
||||||
// import {findThreadGroup} from "@/business/api/model/ThreadGroup";
|
import {findThreadGroup} from "./ThreadGroup";
|
||||||
import {getJmxContent, getLoadConfig, getResourcePools} from "@/api/performance";
|
// import {getJmxContent, getLoadConfig, getResourcePools} from "@/api/load-test";
|
||||||
|
|
||||||
const HANDLER = "handler";
|
const HANDLER = "handler";
|
||||||
const THREAD_GROUP_TYPE = "tgType";
|
const THREAD_GROUP_TYPE = "tgType";
|
||||||
|
@ -390,135 +390,135 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getResourcePools() {
|
getResourcePools() {
|
||||||
getResourcePools(this.isShare)
|
// getResourcePools(this.isShare)
|
||||||
.then(response => {
|
// .then(response => {
|
||||||
this.resourcePools = response.data;
|
// this.resourcePools = response.data;
|
||||||
// 如果当前的资源池无效 设置 null
|
// // 如果当前的资源池无效 设置 null
|
||||||
if (response.data.filter(p => p.id === this.resourcePool).length === 0) {
|
// if (response.data.filter(p => p.id === this.resourcePool && p.performance).length === 0) {
|
||||||
this.resourcePool = null;
|
// this.resourcePool = null;
|
||||||
// 标记因资源池无效而将资源池ID置为null
|
// // 标记因资源池无效而将资源池ID置为null
|
||||||
this.setPoolNull = true;
|
// this.setPoolNull = true;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
this.resourcePoolChange();
|
// this.resourcePoolChange();
|
||||||
});
|
// });
|
||||||
},
|
},
|
||||||
getLoadConfig() {
|
getLoadConfig() {
|
||||||
getLoadConfig(this.testId, this.reportId, this.isShare)
|
// getLoadConfig(this.testId, this.reportId, this.isShare)
|
||||||
.then(response => {
|
// .then(response => {
|
||||||
let data = JSON.parse(response.data);
|
// let data = JSON.parse(response.data);
|
||||||
for (let i = 0; i < this.threadGroups.length; i++) {
|
// for (let i = 0; i < this.threadGroups.length; i++) {
|
||||||
data[i].forEach(item => {
|
// data[i].forEach(item => {
|
||||||
switch (item.key) {
|
// switch (item.key) {
|
||||||
case TARGET_LEVEL:
|
// case TARGET_LEVEL:
|
||||||
this.threadGroups[i].threadNumber = item.value;
|
// this.threadGroups[i].threadNumber = item.value;
|
||||||
break;
|
// break;
|
||||||
case RAMP_UP:
|
// case RAMP_UP:
|
||||||
this.threadGroups[i].rampUpTime = item.value;
|
// this.threadGroups[i].rampUpTime = item.value;
|
||||||
break;
|
// break;
|
||||||
case ITERATE_RAMP_UP:
|
// case ITERATE_RAMP_UP:
|
||||||
this.threadGroups[i].iterateRampUp = item.value;
|
// this.threadGroups[i].iterateRampUp = item.value;
|
||||||
break;
|
// break;
|
||||||
case DURATION:
|
// case DURATION:
|
||||||
this.threadGroups[i].duration = item.value;
|
// this.threadGroups[i].duration = item.value;
|
||||||
break;
|
// break;
|
||||||
case DURATION_HOURS:
|
// case DURATION_HOURS:
|
||||||
this.threadGroups[i].durationHours = item.value;
|
// this.threadGroups[i].durationHours = item.value;
|
||||||
break;
|
// break;
|
||||||
case DURATION_MINUTES:
|
// case DURATION_MINUTES:
|
||||||
this.threadGroups[i].durationMinutes = item.value;
|
// this.threadGroups[i].durationMinutes = item.value;
|
||||||
break;
|
// break;
|
||||||
case DURATION_SECONDS:
|
// case DURATION_SECONDS:
|
||||||
this.threadGroups[i].durationSeconds = item.value;
|
// this.threadGroups[i].durationSeconds = item.value;
|
||||||
break;
|
// break;
|
||||||
case UNIT:
|
// case UNIT:
|
||||||
this.threadGroups[i].unit = item.value;
|
// this.threadGroups[i].unit = item.value;
|
||||||
break;
|
// break;
|
||||||
case STEPS:
|
// case STEPS:
|
||||||
this.threadGroups[i].step = item.value;
|
// this.threadGroups[i].step = item.value;
|
||||||
break;
|
// break;
|
||||||
case RPS_LIMIT:
|
// case RPS_LIMIT:
|
||||||
this.threadGroups[i].rpsLimit = item.value;
|
// this.threadGroups[i].rpsLimit = item.value;
|
||||||
break;
|
// break;
|
||||||
case RPS_LIMIT_ENABLE:
|
// case RPS_LIMIT_ENABLE:
|
||||||
this.threadGroups[i].rpsLimitEnable = item.value;
|
// this.threadGroups[i].rpsLimitEnable = item.value;
|
||||||
break;
|
// break;
|
||||||
case THREAD_TYPE:
|
// case THREAD_TYPE:
|
||||||
this.threadGroups[i].threadType = item.value;
|
// this.threadGroups[i].threadType = item.value;
|
||||||
break;
|
// break;
|
||||||
case ITERATE_NUM:
|
// case ITERATE_NUM:
|
||||||
this.threadGroups[i].iterateNum = item.value;
|
// this.threadGroups[i].iterateNum = item.value;
|
||||||
break;
|
// break;
|
||||||
case ENABLED:
|
// case ENABLED:
|
||||||
this.threadGroups[i].enabled = item.value;
|
// this.threadGroups[i].enabled = item.value;
|
||||||
break;
|
// break;
|
||||||
case DELETED:
|
// case DELETED:
|
||||||
this.threadGroups[i].deleted = item.value;
|
// this.threadGroups[i].deleted = item.value;
|
||||||
break;
|
// break;
|
||||||
case HANDLER:
|
// case HANDLER:
|
||||||
this.threadGroups[i].handler = item.value;
|
// this.threadGroups[i].handler = item.value;
|
||||||
break;
|
// break;
|
||||||
case THREAD_GROUP_TYPE:
|
// case THREAD_GROUP_TYPE:
|
||||||
this.threadGroups[i].tgType = item.value;
|
// this.threadGroups[i].tgType = item.value;
|
||||||
break;
|
// break;
|
||||||
case ON_SAMPLE_ERROR:
|
// case ON_SAMPLE_ERROR:
|
||||||
this.threadGroups[i].onSampleError = item.value;
|
// this.threadGroups[i].onSampleError = item.value;
|
||||||
break;
|
// break;
|
||||||
case STRATEGY:
|
// case STRATEGY:
|
||||||
this.threadGroups[i].strategy = item.value;
|
// this.threadGroups[i].strategy = item.value;
|
||||||
break;
|
// break;
|
||||||
case RESOURCE_NODE_INDEX:
|
// case RESOURCE_NODE_INDEX:
|
||||||
this.threadGroups[i].resourceNodeIndex = item.value;
|
// this.threadGroups[i].resourceNodeIndex = item.value;
|
||||||
break;
|
// break;
|
||||||
case RATIOS:
|
// case RATIOS:
|
||||||
this.threadGroups[i].ratios = item.value;
|
// this.threadGroups[i].ratios = item.value;
|
||||||
break;
|
// break;
|
||||||
case SERIALIZE_THREAD_GROUPS:
|
// case SERIALIZE_THREAD_GROUPS:
|
||||||
this.serializeThreadGroups = item.value;// 所有的线程组值一样
|
// this.serializeThreadGroups = item.value;// 所有的线程组值一样
|
||||||
break;
|
// break;
|
||||||
case AUTO_STOP:
|
// case AUTO_STOP:
|
||||||
this.autoStop = item.value;// 所有的线程组值一样
|
// this.autoStop = item.value;// 所有的线程组值一样
|
||||||
break;
|
// break;
|
||||||
case AUTO_STOP_DELAY:
|
// case AUTO_STOP_DELAY:
|
||||||
this.autoStopDelay = item.value;// 所有的线程组值一样
|
// this.autoStopDelay = item.value;// 所有的线程组值一样
|
||||||
break;
|
// break;
|
||||||
default:
|
// default:
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
//
|
// //
|
||||||
this.$set(this.threadGroups[i], "unit", this.threadGroups[i].unit || 'S');
|
// this.$set(this.threadGroups[i], "unit", this.threadGroups[i].unit || 'S');
|
||||||
this.$set(this.threadGroups[i], "threadType", this.threadGroups[i].threadType || 'DURATION');
|
// this.$set(this.threadGroups[i], "threadType", this.threadGroups[i].threadType || 'DURATION');
|
||||||
this.$set(this.threadGroups[i], "iterateNum", this.threadGroups[i].iterateNum || 1);
|
// this.$set(this.threadGroups[i], "iterateNum", this.threadGroups[i].iterateNum || 1);
|
||||||
this.$set(this.threadGroups[i], "iterateRampUp", this.threadGroups[i].iterateRampUp || 10);
|
// this.$set(this.threadGroups[i], "iterateRampUp", this.threadGroups[i].iterateRampUp || 10);
|
||||||
this.$set(this.threadGroups[i], "enabled", this.threadGroups[i].enabled || 'true');
|
// this.$set(this.threadGroups[i], "enabled", this.threadGroups[i].enabled || 'true');
|
||||||
this.$set(this.threadGroups[i], "deleted", this.threadGroups[i].deleted || 'false');
|
// this.$set(this.threadGroups[i], "deleted", this.threadGroups[i].deleted || 'false');
|
||||||
this.$set(this.threadGroups[i], "onSampleError", this.threadGroups[i].onSampleError || 'continue');
|
// this.$set(this.threadGroups[i], "onSampleError", this.threadGroups[i].onSampleError || 'continue');
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
for (let i = 0; i < this.threadGroups.length; i++) {
|
// for (let i = 0; i < this.threadGroups.length; i++) {
|
||||||
let tg = this.threadGroups[i];
|
// let tg = this.threadGroups[i];
|
||||||
tg.durationHours = Math.floor(tg.duration / 3600);
|
// tg.durationHours = Math.floor(tg.duration / 3600);
|
||||||
tg.durationMinutes = Math.floor((tg.duration / 60 % 60));
|
// tg.durationMinutes = Math.floor((tg.duration / 60 % 60));
|
||||||
tg.durationSeconds = Math.floor((tg.duration % 60));
|
// tg.durationSeconds = Math.floor((tg.duration % 60));
|
||||||
}
|
// }
|
||||||
this.resourcePoolChange();
|
// this.resourcePoolChange();
|
||||||
this.calculateTotalChart();
|
// this.calculateTotalChart();
|
||||||
});
|
// });
|
||||||
},
|
},
|
||||||
getJmxContent() {
|
getJmxContent() {
|
||||||
let threadGroups = [];
|
let threadGroups = [];
|
||||||
getJmxContent(this.testId, this.reportId, this.isShare)
|
// getJmxContent(this.testId, this.reportId, this.isShare)
|
||||||
.then(response => {
|
// .then(response => {
|
||||||
response.data.forEach(d => {
|
// response.data.forEach(d => {
|
||||||
threadGroups = threadGroups.concat(findThreadGroup(d.jmx, d.name));
|
// threadGroups = threadGroups.concat(findThreadGroup(d.jmx, d.name));
|
||||||
threadGroups.forEach(tg => {
|
// threadGroups.forEach(tg => {
|
||||||
tg.options = {};
|
// tg.options = {};
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
this.threadGroups = threadGroups;
|
// this.threadGroups = threadGroups;
|
||||||
this.$emit('fileChange', threadGroups);
|
// this.$emit('fileChange', threadGroups);
|
||||||
this.getLoadConfig();
|
// this.getLoadConfig();
|
||||||
});
|
// });
|
||||||
},
|
},
|
||||||
resourcePoolChange() {
|
resourcePoolChange() {
|
||||||
let result = this.resourcePools.filter(p => p.id === this.resourcePool);
|
let result = this.resourcePools.filter(p => p.id === this.resourcePool);
|
|
@ -37,8 +37,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// 测试计划报告导出的是时候需要引入
|
import LoadCaseReportView from "../load/LoadCaseReportView";
|
||||||
// import LoadCaseReportView from "../../../../../../../../../../performance-test/frontend/src/template/report/performance/share/LoadCaseReportView";
|
|
||||||
|
|
||||||
import TypeTableItem from "../../../../../../common/tableItems/planview/TypeTableItem";
|
import TypeTableItem from "../../../../../../common/tableItems/planview/TypeTableItem";
|
||||||
import MethodTableItem from "../../../../../../common/tableItems/planview/MethodTableItem";
|
import MethodTableItem from "../../../../../../common/tableItems/planview/MethodTableItem";
|
||||||
|
@ -56,7 +55,7 @@ export default {
|
||||||
MsMainContainer,
|
MsMainContainer,
|
||||||
MsAsideContainer,
|
MsAsideContainer,
|
||||||
LoadFailureResult, StatusTableItem, MethodTableItem, TypeTableItem,
|
LoadFailureResult, StatusTableItem, MethodTableItem, TypeTableItem,
|
||||||
// LoadCaseReportView,
|
LoadCaseReportView,
|
||||||
MicroApp
|
MicroApp
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -0,0 +1,226 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<span class="table-title">Errors</span>
|
||||||
|
<el-table
|
||||||
|
:data="tableData"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
:default-sort="{prop: 'elementLabel'}"
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
prop="errorType"
|
||||||
|
label="Type of error"
|
||||||
|
sortable>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
width="200"
|
||||||
|
prop="errorNumber"
|
||||||
|
label="Number of errors"
|
||||||
|
sortable>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
width="200"
|
||||||
|
prop="percentOfErrors"
|
||||||
|
label="% in errors"
|
||||||
|
sortable>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
width="200"
|
||||||
|
prop="percentOfAllSamples"
|
||||||
|
label="% in all samples"
|
||||||
|
sortable>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<span class="table-title">Top 5 Errors</span>
|
||||||
|
<el-table
|
||||||
|
:data="errorSummary"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
show-summary
|
||||||
|
>
|
||||||
|
<el-table-column prop="sample" label="Sample"/>
|
||||||
|
<el-table-column prop="samples" label="#Samples"/>
|
||||||
|
<el-table-column prop="errorsAllSize" label="All Errors"/>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<span class="table-title">#1 Error</span>
|
||||||
|
<el-table
|
||||||
|
:data="errorTop1"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-table-column prop="sample" label="Sample"/>
|
||||||
|
<el-table-column prop="error1" label="#1 Error"/>
|
||||||
|
<el-table-column prop="error1Size" label="#1 Errors Count" width="200"/>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<span class="table-title">#2 Error</span>
|
||||||
|
<el-table
|
||||||
|
:data="errorTop2"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-table-column prop="sample" label="Sample"/>
|
||||||
|
<el-table-column prop="error2" label="#2 Error"/>
|
||||||
|
<el-table-column prop="error2Size" label="#2 Errors Count" width="200"/>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<span class="table-title">#3 Error</span>
|
||||||
|
<el-table
|
||||||
|
:data="errorTop3"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-table-column prop="sample" label="Sample"/>
|
||||||
|
<el-table-column prop="error3" label="#3 Error"/>
|
||||||
|
<el-table-column prop="error3Size" label="#3 Errors Count" width="200"/>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<span class="table-title">#4 Error</span>
|
||||||
|
<el-table
|
||||||
|
:data="errorTop4"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-table-column prop="sample" label="Sample"/>
|
||||||
|
<el-table-column prop="error4" label="#4 Error"/>
|
||||||
|
<el-table-column prop="error4Size" label="#4 Errors Count" width="200"/>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<span class="table-title">#5 Error</span>
|
||||||
|
<el-table
|
||||||
|
:data="errorTop5"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-table-column prop="sample" label="Sample"/>
|
||||||
|
<el-table-column prop="error5" label="#5 Error"/>
|
||||||
|
<el-table-column prop="error5Size" label="#5 Errors Count" width="200"/>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ErrorLog",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tableData: [],
|
||||||
|
errorSummary: [],
|
||||||
|
errorTop1: [],
|
||||||
|
errorTop2: [],
|
||||||
|
errorTop3: [],
|
||||||
|
errorTop4: [],
|
||||||
|
errorTop5: [],
|
||||||
|
id: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: ['report', 'isShare', 'shareId', 'planReportTemplate'],
|
||||||
|
methods: {
|
||||||
|
initTableData() {
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
this.tableData = this.planReportTemplate.reportErrors;
|
||||||
|
this.handleGetTop5(this.planReportTemplate.reportErrorsTop5);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleGetTop5(data) {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.errorTop1 = data
|
||||||
|
.map(e => {
|
||||||
|
return {sample: e.sample, error1: e.error1, error1Size: e.error1Size};
|
||||||
|
})
|
||||||
|
.filter(e => e.error1Size > 0);
|
||||||
|
|
||||||
|
this.errorTop2 = data
|
||||||
|
.map(e => {
|
||||||
|
return {sample: e.sample, error2: e.error2, error2Size: e.error2Size};
|
||||||
|
})
|
||||||
|
.filter(e => e.error2Size > 0);
|
||||||
|
|
||||||
|
this.errorTop3 = data
|
||||||
|
.map(e => {
|
||||||
|
return {sample: e.sample, error3: e.error3, error3Size: e.error3Size};
|
||||||
|
})
|
||||||
|
.filter(e => e.error3Size > 0);
|
||||||
|
|
||||||
|
this.errorTop4 = data
|
||||||
|
.map(e => {
|
||||||
|
return {sample: e.sample, error4: e.error4, error4Size: e.error4Size};
|
||||||
|
})
|
||||||
|
.filter(e => e.error4Size > 0);
|
||||||
|
|
||||||
|
this.errorTop5 = data
|
||||||
|
.map(e => {
|
||||||
|
return {sample: e.sample, error5: e.error5, error5Size: e.error5Size};
|
||||||
|
})
|
||||||
|
.filter(e => e.error5Size > 0);
|
||||||
|
|
||||||
|
this.errorSummary = data.map(e => {
|
||||||
|
return {sample: e.sample, samples: e.samples, errorsAllSize: e.errorsAllSize};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
initData() {
|
||||||
|
this.tableData = [];
|
||||||
|
this.errorTop1 = [];
|
||||||
|
this.errorTop2 = [];
|
||||||
|
this.errorTop3 = [];
|
||||||
|
this.errorTop4 = [];
|
||||||
|
this.errorTop5 = [];
|
||||||
|
this.errorSummary = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
report: {
|
||||||
|
handler(val) {
|
||||||
|
if (!val.status || !val.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let status = val.status;
|
||||||
|
this.id = val.id;
|
||||||
|
if (status === "Completed" || status === "Running") {
|
||||||
|
this.initTableData();
|
||||||
|
} else {
|
||||||
|
this.tableData = [];
|
||||||
|
this.errorTop1 = [];
|
||||||
|
this.errorTop2 = [];
|
||||||
|
this.errorTop3 = [];
|
||||||
|
this.errorTop4 = [];
|
||||||
|
this.errorTop5 = [];
|
||||||
|
this.errorSummary = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
planReportTemplate: {
|
||||||
|
handler() {
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
this.initTableData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.table-title {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #8492a6;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,300 @@
|
||||||
|
<template>
|
||||||
|
<ms-container>
|
||||||
|
<el-main>
|
||||||
|
<el-card v-loading="loading" v-if="show">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="16">
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<div v-if="isPlanReport" style="float: right;margin-right: 10px;">
|
||||||
|
<div 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>
|
||||||
|
<div v-show="showMoreProjectEnvMap">
|
||||||
|
<el-link icon="el-icon-more" @click="showAllProjectInfo"></el-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="float: left">
|
||||||
|
<span class="ms-report-time-desc">
|
||||||
|
{{
|
||||||
|
$t('report.test_duration', [templateMinutes ? templateMinutes : minutes,
|
||||||
|
templateSeconds ? templateSeconds : seconds])
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span class="ms-report-time-desc" v-if="startTime !== '0'">
|
||||||
|
{{ $t('report.test_start_time') }}:{{ startTime | datetimeFormat }}
|
||||||
|
</span>
|
||||||
|
<span class="ms-report-time-desc" v-else-if="planReportTemplate && planReportTemplate.startTime">
|
||||||
|
{{ $t('report.test_start_time') }}:{{ planReportTemplate.startTime | datetimeFormat }}
|
||||||
|
</span>
|
||||||
|
<span class="ms-report-time-desc" v-else>
|
||||||
|
{{ $t('report.test_start_time') }}:-
|
||||||
|
</span>
|
||||||
|
<span class="ms-report-time-desc" v-if="report.status === 'Completed' && endTime !== '0'">
|
||||||
|
{{ $t('report.test_end_time') }}:{{ endTime | datetimeFormat }}
|
||||||
|
</span>
|
||||||
|
<span class="ms-report-time-desc" v-else-if="planReportTemplate && planReportTemplate.endTime">
|
||||||
|
{{ $t('report.test_end_time') }}:{{ planReportTemplate.endTime | datetimeFormat }}
|
||||||
|
</span>
|
||||||
|
<span class="ms-report-time-desc" v-else>
|
||||||
|
{{ $t('report.test_end_time') }}:-
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-divider/>
|
||||||
|
<div ref="resume">
|
||||||
|
<el-tabs v-model="active">
|
||||||
|
<el-tab-pane :label="$t('report.test_overview')">
|
||||||
|
<ms-report-test-overview :report="report" :is-share="isShare" :plan-report-template="planReportTemplate"
|
||||||
|
:share-id="shareId" ref="testOverview"/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane :label="$t('report.test_details')">
|
||||||
|
<ms-report-test-details :report="report" :is-share="isShare" :plan-report-template="planReportTemplate"
|
||||||
|
:share-id="shareId" ref="testDetails"/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane :label="$t('report.test_request_statistics')">
|
||||||
|
<ms-report-request-statistics :report="report" :is-share="isShare"
|
||||||
|
:plan-report-template="planReportTemplate"
|
||||||
|
:share-id="shareId" ref="requestStatistics"/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane :label="$t('report.test_error_log')">
|
||||||
|
<ms-report-error-log :report="report" :is-share="isShare" :plan-report-template="planReportTemplate"
|
||||||
|
:share-id="shareId" ref="errorLog"/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane :label="$t('report.test_log_details')">
|
||||||
|
<ms-report-log-details :report="report" :is-share="isShare" :plan-report-template="planReportTemplate"
|
||||||
|
:share-id="shareId"/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane :label="$t('report.test_monitor_details')">
|
||||||
|
<monitor-card :report="report" :is-share="isShare" :plan-report-template="planReportTemplate"
|
||||||
|
:share-id="shareId"/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane :label="$t('report.test_config')">
|
||||||
|
<ms-test-configuration :report-id="reportId" :report="report"
|
||||||
|
:plan-report-template="planReportTemplate"
|
||||||
|
:is-share="isShare" :share-id="shareId"/>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<project-environment-dialog ref="projectEnvDialog"></project-environment-dialog>
|
||||||
|
</el-main>
|
||||||
|
</ms-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import MsReportErrorLog from "./ErrorLog";
|
||||||
|
import MsReportLogDetails from "./LogDetails";
|
||||||
|
import MsReportRequestStatistics from "./RequestStatistics";
|
||||||
|
import MsReportTestOverview from "./TestOverview";
|
||||||
|
import MsContainer from "metersphere-frontend/src/components/MsContainer";
|
||||||
|
import MsMainContainer from "metersphere-frontend/src/components/MsMainContainer";
|
||||||
|
import MonitorCard from "./MonitorCard";
|
||||||
|
import MsReportTestDetails from './TestDetails';
|
||||||
|
import ProjectEnvironmentDialog from "./ProjectEnvironmentDialog";
|
||||||
|
import MsTag from "metersphere-frontend/src/components/MsTag";
|
||||||
|
import MsTestConfiguration from "./TestConfiguration";
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "LoadCaseReportView",
|
||||||
|
components: {
|
||||||
|
MsTestConfiguration,
|
||||||
|
MonitorCard,
|
||||||
|
MsReportErrorLog,
|
||||||
|
MsReportLogDetails,
|
||||||
|
MsReportRequestStatistics,
|
||||||
|
MsReportTestOverview,
|
||||||
|
MsReportTestDetails,
|
||||||
|
MsContainer,
|
||||||
|
MsMainContainer,
|
||||||
|
ProjectEnvironmentDialog,
|
||||||
|
MsTag,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
active: '0',
|
||||||
|
status: '',
|
||||||
|
reportName: '',
|
||||||
|
testId: '',
|
||||||
|
testName: '',
|
||||||
|
projectId: '',
|
||||||
|
projectName: '',
|
||||||
|
startTime: '0',
|
||||||
|
endTime: '0',
|
||||||
|
minutes: '0',
|
||||||
|
seconds: '0',
|
||||||
|
title: 'Logging',
|
||||||
|
projectEnvMap: null,
|
||||||
|
showMoreProjectEnvMap: false,
|
||||||
|
allProjectEnvMap: null,
|
||||||
|
report: {},
|
||||||
|
websocket: null,
|
||||||
|
dialogFormVisible: false,
|
||||||
|
testPlan: {testResourcePoolId: null},
|
||||||
|
show: true,
|
||||||
|
test: {testResourcePoolId: null},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
reportId: String,
|
||||||
|
isReadOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isPlanReport: Boolean,
|
||||||
|
isShare: Boolean,
|
||||||
|
shareId: String,
|
||||||
|
planReportTemplate: Object
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
reportId() {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
showProjectEnv() {
|
||||||
|
return this.projectEnvMap && JSON.stringify(this.projectEnvMap) !== '{}';
|
||||||
|
},
|
||||||
|
templateMinutes() {
|
||||||
|
if (this.planReportTemplate && this.planReportTemplate.duration) {
|
||||||
|
let duration = this.planReportTemplate.duration;
|
||||||
|
return Math.floor(duration / 60);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
templateSeconds() {
|
||||||
|
if (this.planReportTemplate && this.planReportTemplate.duration) {
|
||||||
|
let duration = this.planReportTemplate.duration;
|
||||||
|
return duration % 60;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showAllProjectInfo() {
|
||||||
|
this.$refs.projectEnvDialog.open(this.allProjectEnvMap);
|
||||||
|
},
|
||||||
|
isProjectEnvShowMore(projectEnvMap) {
|
||||||
|
this.showMoreProjectEnvMap = false;
|
||||||
|
this.projectEnvMap = {};
|
||||||
|
if (projectEnvMap) {
|
||||||
|
let keySize = 0;
|
||||||
|
for (let key in projectEnvMap) {
|
||||||
|
keySize++;
|
||||||
|
if (keySize > 1) {
|
||||||
|
this.showMoreProjectEnvMap = true;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.projectEnvMap = {};
|
||||||
|
this.$set(this.projectEnvMap, key, projectEnvMap[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initBreadcrumb(callback) {
|
||||||
|
if (this.isPlanReport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initReportTimeInfo() {
|
||||||
|
if (this.status === 'Starting') {
|
||||||
|
this.clearData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
this.handleInitReportTimeInfo(this.planReportTemplate);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleInitReportTimeInfo(data) {
|
||||||
|
if (data) {
|
||||||
|
this.startTime = data.startTime;
|
||||||
|
this.endTime = data.endTime;
|
||||||
|
let duration = data.duration;
|
||||||
|
this.minutes = Math.floor(duration / 60);
|
||||||
|
this.seconds = duration % 60;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkReportStatus(status) {
|
||||||
|
switch (status) {
|
||||||
|
case 'Error':
|
||||||
|
// this.$warning(this.$t('report.generation_error'));
|
||||||
|
this.active = '4';
|
||||||
|
break;
|
||||||
|
case 'Starting':
|
||||||
|
this.$alert(this.$t('report.start_status'));
|
||||||
|
break;
|
||||||
|
case 'Reporting':
|
||||||
|
case 'Running':
|
||||||
|
case 'Completed':
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clearData() {
|
||||||
|
this.show = false;
|
||||||
|
this.startTime = '0';
|
||||||
|
this.endTime = '0';
|
||||||
|
this.minutes = '0';
|
||||||
|
this.seconds = '0';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.show = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
this.clearData();
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
this.handleInit(this.planReportTemplate);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleInit(data) {
|
||||||
|
if (data) {
|
||||||
|
this.allProjectEnvMap = data.projectEnvMap;
|
||||||
|
this.isProjectEnvShowMore(data.projectEnvMap);
|
||||||
|
this.status = data.status;
|
||||||
|
this.$set(this, "report", data);
|
||||||
|
this.$set(this.test, "testResourcePoolId", data.testResourcePoolId);
|
||||||
|
this.checkReportStatus(data.status);
|
||||||
|
if (this.status === "Completed" || this.status === "Running") {
|
||||||
|
this.initReportTimeInfo();
|
||||||
|
}
|
||||||
|
this.initBreadcrumb();
|
||||||
|
} else {
|
||||||
|
this.$error(this.$t('report.not_exist'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.ms-report-view-btns {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-report-time-desc {
|
||||||
|
text-align: left;
|
||||||
|
display: block;
|
||||||
|
color: #5C7878;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-report-time-desc-share {
|
||||||
|
text-align: left;
|
||||||
|
color: #5C7878;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
<template>
|
||||||
|
<el-card>
|
||||||
|
<template v-slot:header>
|
||||||
|
<span class="title">Load</span>
|
||||||
|
</template>
|
||||||
|
<div v-for="(option, index) in loadList" :key="index">
|
||||||
|
<ms-chart ref="chart1" :options="option" :autoresize="true"></ms-chart>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MsChart from "metersphere-frontend/src/components/chart/MsChart";
|
||||||
|
import {getPerformanceReportLoadChart} from "@/api/load-test";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "LoadCompareCard",
|
||||||
|
components: {MsChart},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loadList: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initCard() {
|
||||||
|
this.loadList = [];
|
||||||
|
this.reportId = this.$route.path.split('/')[4];
|
||||||
|
this.compareReports = JSON.parse(sessionStorage.getItem("compareReports"));
|
||||||
|
|
||||||
|
this.compareReports.forEach(report => {
|
||||||
|
this.initOverview(report);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
initOverview(report) {
|
||||||
|
getPerformanceReportLoadChart(report.id)
|
||||||
|
.then(({data}) => {
|
||||||
|
let yAxisList = data.filter(m => m.yAxis2 === -1).map(m => m.yAxis);
|
||||||
|
let yAxis2List = data.filter(m => m.yAxis === -1).map(m => m.yAxis2);
|
||||||
|
let yAxisListMax = this._getChartMax(yAxisList);
|
||||||
|
let yAxis2ListMax = this._getChartMax(yAxis2List);
|
||||||
|
|
||||||
|
let yAxisIndex0List = data.filter(m => m.yAxis2 === -1).map(m => m.groupName);
|
||||||
|
yAxisIndex0List = this._unique(yAxisIndex0List);
|
||||||
|
let yAxisIndex1List = data.filter(m => m.yAxis === -1).map(m => m.groupName);
|
||||||
|
yAxisIndex1List = this._unique(yAxisIndex1List);
|
||||||
|
|
||||||
|
let loadOption = {
|
||||||
|
title: {
|
||||||
|
text: report.name + " " + this.$options.filters['datetimeFormat'](report.createTime),
|
||||||
|
left: 'center',
|
||||||
|
top: 20,
|
||||||
|
textStyle: {
|
||||||
|
color: '#65A2FF'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: 'axis',
|
||||||
|
// extraCssText: 'z-index: 999;',
|
||||||
|
confine: true,
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
xAxis: {},
|
||||||
|
yAxis: [{
|
||||||
|
name: 'User',
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: yAxisListMax,
|
||||||
|
splitNumber: 5,
|
||||||
|
interval: yAxisListMax / 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Transactions/s',
|
||||||
|
type: 'value',
|
||||||
|
splitNumber: 5,
|
||||||
|
min: 0,
|
||||||
|
max: yAxis2ListMax,
|
||||||
|
interval: yAxis2ListMax / 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: []
|
||||||
|
};
|
||||||
|
let setting = {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'users',
|
||||||
|
color: '#0CA74A',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'hits',
|
||||||
|
yAxisIndex: '1',
|
||||||
|
color: '#65A2FF',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'errors',
|
||||||
|
yAxisIndex: '1',
|
||||||
|
color: '#E6113C',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
yAxisIndex0List.forEach(item => {
|
||||||
|
setting["series"].splice(0, 0, {name: item, yAxisIndex: '0'})
|
||||||
|
})
|
||||||
|
|
||||||
|
yAxisIndex1List.forEach(item => {
|
||||||
|
setting["series"].splice(0, 0, {name: item, yAxisIndex: '1'})
|
||||||
|
})
|
||||||
|
this.loadList.push(this.generateOption(loadOption, data, setting));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.loadList = [];
|
||||||
|
})
|
||||||
|
},
|
||||||
|
generateOption(option, data, setting) {
|
||||||
|
let chartData = data;
|
||||||
|
let seriesArray = [];
|
||||||
|
for (let set in setting) {
|
||||||
|
if (set === "series") {
|
||||||
|
seriesArray = setting[set];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.$set(option, set, setting[set]);
|
||||||
|
}
|
||||||
|
let legend = [], series = {}, xAxis = [], seriesData = [];
|
||||||
|
chartData.forEach(item => {
|
||||||
|
if (!xAxis.includes(item.xAxis)) {
|
||||||
|
xAxis.push(item.xAxis);
|
||||||
|
}
|
||||||
|
xAxis.sort()
|
||||||
|
let name = item.groupName
|
||||||
|
if (!legend.includes(name)) {
|
||||||
|
legend.push(name)
|
||||||
|
series[name] = []
|
||||||
|
}
|
||||||
|
if (item.yAxis === -1) {
|
||||||
|
series[name].splice(xAxis.indexOf(item.xAxis), 0, [item.xAxis, item.yAxis2.toFixed(2)]);
|
||||||
|
} else {
|
||||||
|
series[name].splice(xAxis.indexOf(item.xAxis), 0, [item.xAxis, item.yAxis.toFixed(2)]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.$set(option.legend, "data", legend);
|
||||||
|
this.$set(option.legend, "type", "scroll");
|
||||||
|
this.$set(option.legend, "bottom", "10px");
|
||||||
|
this.$set(option.xAxis, "data", xAxis);
|
||||||
|
for (let name in series) {
|
||||||
|
let d = series[name];
|
||||||
|
d.sort((a, b) => a[0].localeCompare(b[0]));
|
||||||
|
let items = {
|
||||||
|
name: name,
|
||||||
|
type: 'line',
|
||||||
|
data: d,
|
||||||
|
smooth: true
|
||||||
|
};
|
||||||
|
let seriesArrayNames = seriesArray.map(m => m.name);
|
||||||
|
if (seriesArrayNames.includes(name)) {
|
||||||
|
for (let j = 0; j < seriesArray.length; j++) {
|
||||||
|
let seriesObj = seriesArray[j];
|
||||||
|
if (seriesObj['name'] === name) {
|
||||||
|
Object.assign(items, seriesObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seriesData.push(items);
|
||||||
|
}
|
||||||
|
this.$set(option, "series", seriesData);
|
||||||
|
return option;
|
||||||
|
},
|
||||||
|
_getChartMax(arr) {
|
||||||
|
const max = Math.max(...arr);
|
||||||
|
return Math.ceil(max / 4.5) * 5;
|
||||||
|
},
|
||||||
|
_unique(arr) {
|
||||||
|
return Array.from(new Set(arr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.echarts {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,144 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-row :gutter="10">
|
||||||
|
<el-col :span="4">
|
||||||
|
<el-select v-model="currentInstance" placeholder="" size="small" style="width: 100%"
|
||||||
|
@change="changeInstance(currentInstance)">
|
||||||
|
<el-option
|
||||||
|
v-for="item in resource"
|
||||||
|
:key="item.resourceId"
|
||||||
|
:label="item.resourceName"
|
||||||
|
:value="item.resourceId">
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="20">
|
||||||
|
<div class="logging-content" v-loading="loading">
|
||||||
|
<ul class="infinite-list">
|
||||||
|
<li class="infinite-list-item" v-for="(log, index) in logContent"
|
||||||
|
:key="currentInstance+index">
|
||||||
|
{{ log.content }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "LogDetails",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
resource: [],
|
||||||
|
logContent: [],
|
||||||
|
result: {},
|
||||||
|
id: '',
|
||||||
|
page: 1,
|
||||||
|
pageCount: 5,
|
||||||
|
loading: false,
|
||||||
|
currentInstance: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: ['report', 'export', 'isShare', 'shareId', 'planReportTemplate'],
|
||||||
|
methods: {
|
||||||
|
getResource() {
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
this.handleGetLogResource(this.planReportTemplate.reportLogResource);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleGetLogResource(data) {
|
||||||
|
this.resource = data;
|
||||||
|
if (!this.currentInstance) {
|
||||||
|
this.currentInstance = this.resource[0]?.resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
if (this.currentInstance) {
|
||||||
|
this.changeInstance(this.currentInstance);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
load(resourceId) {
|
||||||
|
if (this.loading || this.page > this.pageCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
let {reportLogResource} = this.planReportTemplate;
|
||||||
|
if (reportLogResource && reportLogResource.length > 0) {
|
||||||
|
let {reportLogs} = reportLogResource[0];
|
||||||
|
if (reportLogs) {
|
||||||
|
this.handleGetPlanTemplateLog(reportLogs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleGetPlanTemplateLog(data) {
|
||||||
|
data.forEach(log => {
|
||||||
|
if (this.logContent) {
|
||||||
|
this.logContent.push(log);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
changeInstance(instance) {
|
||||||
|
this.currentInstance = instance;
|
||||||
|
this.loading = false;
|
||||||
|
this.page = 1;
|
||||||
|
this.logContent = [];
|
||||||
|
this.load(instance);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'$route'(to) {
|
||||||
|
if (to.name === "perReportView") {
|
||||||
|
this.id = to.path.split('/')[4];
|
||||||
|
this.getResource();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
report: {
|
||||||
|
handler(val) {
|
||||||
|
if (!val.status || !val.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let status = val.status;
|
||||||
|
this.id = val.id;
|
||||||
|
if (status === "Completed" || status === "Running") {
|
||||||
|
this.getResource();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
planReportTemplate: {
|
||||||
|
handler() {
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
this.getResource();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.logging-content {
|
||||||
|
white-space: pre-line;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infinite-list {
|
||||||
|
height: calc(100vh - 205px);
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
overflow: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
.infinite-list-item {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,348 @@
|
||||||
|
<template>
|
||||||
|
<div v-loading="result.loading">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="4">
|
||||||
|
<div>
|
||||||
|
<el-select v-model="currentInstance" placeholder="" size="small" style="width: 100%"
|
||||||
|
@change="getResource(currentInstance)">
|
||||||
|
<el-option
|
||||||
|
v-for="item in instances"
|
||||||
|
:key="item.ip+item.port"
|
||||||
|
:value="item.ip+':'+item.port">
|
||||||
|
{{ item.ip }} {{ item.name }}
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="padding-top: 10px">
|
||||||
|
<el-checkbox-group v-model="checkList"
|
||||||
|
@change="handleCheckListChange(currentInstance)">
|
||||||
|
<div v-for="op in checkOptions"
|
||||||
|
:key="op.key"
|
||||||
|
:content="op.label">
|
||||||
|
<el-checkbox :label="op.label"/>
|
||||||
|
</div>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="20">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<ms-chart v-if="showChart" ref="chart2" class="chart-config" @datazoom="changeDataZoom"
|
||||||
|
:options="totalOption"
|
||||||
|
:autoresize="true"></ms-chart>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<el-col :offset="2" :span="20">
|
||||||
|
<el-table
|
||||||
|
:data="tableData"
|
||||||
|
stripe
|
||||||
|
border
|
||||||
|
style="width: 100%">
|
||||||
|
<el-table-column label="Label" align="center">
|
||||||
|
<el-table-column
|
||||||
|
prop="label"
|
||||||
|
label="Label"
|
||||||
|
sortable>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Aggregate" align="center">
|
||||||
|
<el-table-column
|
||||||
|
prop="avg"
|
||||||
|
label="Avg."
|
||||||
|
width="100"
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="min"
|
||||||
|
label="Min."
|
||||||
|
width="100"
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="max"
|
||||||
|
label="Max."
|
||||||
|
width="100"
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Range" align="center">
|
||||||
|
<el-table-column
|
||||||
|
prop="startTime"
|
||||||
|
label="Start"
|
||||||
|
width="160"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="endTime"
|
||||||
|
label="End"
|
||||||
|
width="160"
|
||||||
|
/>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import MsChart from "metersphere-frontend/src/components/chart/MsChart";
|
||||||
|
|
||||||
|
const color = ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'];
|
||||||
|
const checkList = ['CPU', 'Memory', 'Disk', 'Network In', 'Network Out'];
|
||||||
|
const checkOptions = [
|
||||||
|
{key: 'cpu', label: 'CPU'},
|
||||||
|
{key: 'memory', label: 'Memory'},
|
||||||
|
{key: 'disk', label: 'Disk'},
|
||||||
|
{key: 'netIn', label: 'Network In'},
|
||||||
|
{key: 'netOut', label: 'Network Out'}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "MonitorCard",
|
||||||
|
props: ['report', 'export', 'isShare', 'shareId', 'planReportTemplate'],
|
||||||
|
components: {MsChart},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeNames: '0',
|
||||||
|
result: {},
|
||||||
|
id: '',
|
||||||
|
init: false,
|
||||||
|
loading: false,
|
||||||
|
currentInstance: '',
|
||||||
|
instances: [],
|
||||||
|
data: [],
|
||||||
|
tableData: [],
|
||||||
|
checkList: checkList,
|
||||||
|
checkOptions: checkOptions,
|
||||||
|
showChart: true,
|
||||||
|
baseOption: {
|
||||||
|
color: color,
|
||||||
|
grid: {
|
||||||
|
// right: '35%' // 动态改这个值
|
||||||
|
},
|
||||||
|
title: {},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
y: 'top'
|
||||||
|
},
|
||||||
|
xAxis: {type: 'category'},
|
||||||
|
yAxis: [{
|
||||||
|
name: 'Usage(%)',
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
}, {
|
||||||
|
type: 'value',
|
||||||
|
name: 'kb/s',
|
||||||
|
min: 0,
|
||||||
|
}],
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: 0,
|
||||||
|
end: 20
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: []
|
||||||
|
},
|
||||||
|
totalOption: {},
|
||||||
|
seriesData: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.data = [];
|
||||||
|
this.instances = [];
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getResource(currentInstance) {
|
||||||
|
// this.init = true;
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
this.instances = this.planReportTemplate.reportResource;
|
||||||
|
this.currentInstance = currentInstance || this.instances[0].ip + ":" + this.instances[0].port;
|
||||||
|
this.data = this.planReportTemplate.metricData;
|
||||||
|
this.totalOption = this.getOption(this.currentInstance);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleChecked(id) {
|
||||||
|
let curr = this.instances.filter(instance => id === instance.ip + ":" + instance.port)[0];
|
||||||
|
if (curr && curr.monitorConfig) {
|
||||||
|
this.checkList = [];
|
||||||
|
this.checkOptions = curr.monitorConfig.filter(mc => mc.value && mc.name)
|
||||||
|
.map(mc => {
|
||||||
|
this.checkList.push(mc.name);
|
||||||
|
return {key: mc.name, label: mc.name,};
|
||||||
|
});
|
||||||
|
if (this.checkList.length === 0) {
|
||||||
|
this.checkList = checkList;
|
||||||
|
this.checkOptions = checkOptions;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.checkOptions = checkOptions;
|
||||||
|
this.checkList = checkList;
|
||||||
|
}
|
||||||
|
this.totalOption = {};
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.totalOption = this.getOption(id);
|
||||||
|
this.changeDataZoom({start: 0, end: 100});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleCheckListChange(id) {
|
||||||
|
this.totalOption = {};
|
||||||
|
this.showChart = false;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.showChart = true;
|
||||||
|
this.totalOption = this.getOption(id);
|
||||||
|
this.changeDataZoom({start: 0, end: 100});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getOption(id) {
|
||||||
|
let legend = [];
|
||||||
|
let series = [];
|
||||||
|
|
||||||
|
for (const name of this.checkList) {
|
||||||
|
let check = this.checkOptions.filter(op => op.label === name)[0].key;
|
||||||
|
let yAxisIndex = 1;
|
||||||
|
if (check === 'cpu' || check === 'memory' || check === 'disk') {
|
||||||
|
yAxisIndex = 0;
|
||||||
|
}
|
||||||
|
this.data.forEach(d => {
|
||||||
|
if (d.instance === id && d.seriesName === check) {
|
||||||
|
if (legend.indexOf(name) > -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.baseOption.xAxis.data = d.timestamps;
|
||||||
|
|
||||||
|
let yAxis = d.values.map(v => v.toFixed(2));
|
||||||
|
let data = [];
|
||||||
|
for (let i = 0; i < d.timestamps.length; i++) {
|
||||||
|
data.push([d.timestamps[i], yAxis[i]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
legend.push(name);
|
||||||
|
series.push({
|
||||||
|
name: name,
|
||||||
|
data: data,
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: yAxisIndex,
|
||||||
|
smooth: true,
|
||||||
|
sampling: 'lttb',
|
||||||
|
showSymbol: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.seriesData = series;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.baseOption.legend.data = legend;
|
||||||
|
this.baseOption.series = series;
|
||||||
|
return this.baseOption;
|
||||||
|
},
|
||||||
|
changeDataZoom(params) {
|
||||||
|
let start = params.start / 100;
|
||||||
|
let end = params.end / 100;
|
||||||
|
if (params.batch) {
|
||||||
|
start = params.batch[0].start / 100;
|
||||||
|
end = params.batch[0].end / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tableData = [];
|
||||||
|
for (let i = 0; i < this.seriesData.length; i++) {
|
||||||
|
let sub = this.seriesData[i].data, label = this.seriesData[i].name;
|
||||||
|
let len = 0;
|
||||||
|
let min, avg, max, sum = 0, startTime, endTime;
|
||||||
|
for (let j = 0; j < sub.length; j++) {
|
||||||
|
let time = sub[j][0];
|
||||||
|
let value = Number.parseFloat(sub[j][1]);
|
||||||
|
let index = (j / (sub.length - 1)).toFixed(2);
|
||||||
|
if (index < start) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (index >= end) {
|
||||||
|
endTime = time;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startTime) {
|
||||||
|
startTime = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!min && !max) {
|
||||||
|
min = max = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (min > value) {
|
||||||
|
min = value;
|
||||||
|
}
|
||||||
|
if (max < value) {
|
||||||
|
max = value;
|
||||||
|
}
|
||||||
|
sum += value;
|
||||||
|
|
||||||
|
len++; // 实际 len
|
||||||
|
}
|
||||||
|
|
||||||
|
avg = (sum / len).toFixed(2);
|
||||||
|
tableData.push({label, min, max, avg, startTime, endTime});
|
||||||
|
}
|
||||||
|
this.tableData = tableData;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
report: {
|
||||||
|
handler(val) {
|
||||||
|
if (!val.status || !val.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let status = val.status;
|
||||||
|
this.id = val.id;
|
||||||
|
if (this.init) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (status === "Completed" || status === "Running") {
|
||||||
|
this.getResource();
|
||||||
|
} else {
|
||||||
|
this.instances = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
planReportTemplate: {
|
||||||
|
handler() {
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
this.getResource();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chart-config {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-detail {
|
||||||
|
height: calc(100vh - 375px);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-checkbox__label ) {
|
||||||
|
font-size: 10px !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,70 @@
|
||||||
|
<template>
|
||||||
|
<el-card class="table-card">
|
||||||
|
<template v-slot:header>
|
||||||
|
<span class="title">Overview</span>
|
||||||
|
</template>
|
||||||
|
<el-table border :data="overviewList" class="adjust-table test-content">
|
||||||
|
<el-table-column prop="name" :label="$t('commons.name')"/>
|
||||||
|
<el-table-column prop="createTime" :label="$t('commons.create_time')">
|
||||||
|
<template v-slot:default="scope">
|
||||||
|
<span>{{ scope.row.createTime | datetimeFormat }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="maxUsers" label="Max Users"/>
|
||||||
|
<el-table-column prop="avgTransactions" label="Avg.Transactions"/>
|
||||||
|
<el-table-column prop="errors" label="Errors"/>
|
||||||
|
<el-table-column prop="avgResponseTime" label="Avg.Response Time"/>
|
||||||
|
<el-table-column prop="responseTime90" label="90% Response Time"/>
|
||||||
|
<el-table-column prop="avgBandwidth" label="Avg.Bandwidth"/>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {getOverview} from "@/api/report";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "OverviewCompareCard",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
reportId: null,
|
||||||
|
compareReports: [],
|
||||||
|
overviewList: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initTable() {
|
||||||
|
this.overviewList = [];
|
||||||
|
|
||||||
|
this.reportId = this.$route.path.split('/')[4];
|
||||||
|
this.compareReports = JSON.parse(sessionStorage.getItem("compareReports"));
|
||||||
|
|
||||||
|
this.compareReports.forEach(report => {
|
||||||
|
this.initOverview(report);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
initOverview(report) {
|
||||||
|
getOverview(report.id)
|
||||||
|
.then(({data}) => {
|
||||||
|
this.overviewList.push({
|
||||||
|
name: report.name,
|
||||||
|
createTime: report.createTime,
|
||||||
|
maxUsers: data.maxUsers,
|
||||||
|
avgThroughput: data.avgThroughput,
|
||||||
|
avgTransactions: data.avgTransactions,
|
||||||
|
errors: data.errors,
|
||||||
|
avgResponseTime: data.avgResponseTime,
|
||||||
|
responseTime90: data.responseTime90,
|
||||||
|
avgBandwidth: data.avgBandwidth,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,145 @@
|
||||||
|
<template>
|
||||||
|
<ms-chart :options="bar"></ms-chart>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import echarts from 'echarts'
|
||||||
|
import MsChart from "metersphere-frontend/src/components/chart/MsChart";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "PerformanceChart",
|
||||||
|
components: {MsChart},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
bar: {
|
||||||
|
|
||||||
|
backgroundColor: '#394056',
|
||||||
|
title: {
|
||||||
|
top: 20,
|
||||||
|
text: 'Requests',
|
||||||
|
textStyle: {
|
||||||
|
fontWeight: 'normal',
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#F1F1F3'
|
||||||
|
},
|
||||||
|
left: '1%'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#57617B'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
top: 20,
|
||||||
|
icon: 'rect',
|
||||||
|
itemWidth: 14,
|
||||||
|
itemHeight: 5,
|
||||||
|
itemGap: 13,
|
||||||
|
data: ['CMCC', 'CTCC', 'CUCC'],
|
||||||
|
right: '4%',
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#F1F1F3'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 100,
|
||||||
|
left: '2%',
|
||||||
|
right: '2%',
|
||||||
|
bottom: '2%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#57617B'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: ['13:00', '13:05', '13:10', '13:15', '13:20', '13:25', '13:30', '13:35', '13:40', '13:45', '13:50', '13:55']
|
||||||
|
}],
|
||||||
|
yAxis: [{
|
||||||
|
type: 'value',
|
||||||
|
name: '(%)',
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#57617B'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
margin: 10,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 14
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#57617B'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
series: [{
|
||||||
|
name: 'CMCC',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 5,
|
||||||
|
showSymbol: false,
|
||||||
|
lineStyle: {
|
||||||
|
width: 1
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgb(137,189,27)',
|
||||||
|
borderColor: 'rgba(137,189,2,0.27)',
|
||||||
|
borderWidth: 12
|
||||||
|
},
|
||||||
|
data: [220, 182, 191, 134, 150, 120, 110, 125, 145, 122, 165, 122]
|
||||||
|
}, {
|
||||||
|
name: 'CTCC',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 5,
|
||||||
|
showSymbol: false,
|
||||||
|
lineStyle: {
|
||||||
|
width: 1
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgb(0,136,212)',
|
||||||
|
borderColor: 'rgba(0,136,212,0.2)',
|
||||||
|
borderWidth: 12
|
||||||
|
},
|
||||||
|
data: [120, 110, 125, 145, 122, 165, 122, 220, 182, 191, 134, 150]
|
||||||
|
}, {
|
||||||
|
name: 'CUCC',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 5,
|
||||||
|
showSymbol: false,
|
||||||
|
lineStyle: {
|
||||||
|
width: 1
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgb(219,50,51)',
|
||||||
|
borderColor: 'rgba(219,50,51,0.2)',
|
||||||
|
borderWidth: 12
|
||||||
|
},
|
||||||
|
data: [220, 182, 125, 145, 122, 191, 134, 150, 120, 110, 165, 122]
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,49 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('commons.environment')"
|
||||||
|
:visible.sync="dialogVisible"
|
||||||
|
width="30%"
|
||||||
|
:append-to-body="true"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
:before-close="handleClose">
|
||||||
|
<div>
|
||||||
|
<div v-if="projectEnvMap" type="flex">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import MsTag from "metersphere-frontend/src/components/MsTag";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProjectEnvironmentDialog",
|
||||||
|
components: {MsTag},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
projectEnvMap: {},
|
||||||
|
dialogVisible: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClose() {
|
||||||
|
this.dialogVisible = false;
|
||||||
|
this.projectEnvMap = {};
|
||||||
|
},
|
||||||
|
open(projectEnvMap) {
|
||||||
|
this.dialogVisible = true;
|
||||||
|
this.projectEnvMap = projectEnvMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,200 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-table
|
||||||
|
:data="tableData"
|
||||||
|
stripe
|
||||||
|
border
|
||||||
|
height="calc(100vh - 190px)"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-table-column label="Requests" min-width="150" align="center">
|
||||||
|
<el-table-column
|
||||||
|
prop="label"
|
||||||
|
label="Label"
|
||||||
|
sortable
|
||||||
|
min-width="150">
|
||||||
|
<template v-slot:header="{column}">
|
||||||
|
<span>Label</span>
|
||||||
|
<i class="el-icon-search" style="margin-left: 8px;cursor: pointer;font-weight: bold;"
|
||||||
|
@click="click(column)"></i>
|
||||||
|
<el-input v-model="searchLabel"
|
||||||
|
placeholder="请输入 Label 搜索"
|
||||||
|
size="mini"
|
||||||
|
class="search_input"
|
||||||
|
style="width: 100px; margin-left: 5px"
|
||||||
|
v-if="column.showSearch"
|
||||||
|
clearable
|
||||||
|
@clear="filterLabel"
|
||||||
|
@keyup.enter.native="filterLabel"/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="Executions" align="center">
|
||||||
|
<el-table-column
|
||||||
|
prop="samples"
|
||||||
|
label="Samples"
|
||||||
|
sortable
|
||||||
|
width="110"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<el-table-column
|
||||||
|
prop="fail"
|
||||||
|
label="FAIL"
|
||||||
|
sortable
|
||||||
|
align="center"
|
||||||
|
min-width="60"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<el-table-column
|
||||||
|
prop="error"
|
||||||
|
label="Error%"
|
||||||
|
sortable
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="Response Times(ms)" align="center">
|
||||||
|
<el-table-column
|
||||||
|
prop="average"
|
||||||
|
label="Avg"
|
||||||
|
sortable
|
||||||
|
min-width="60"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="min"
|
||||||
|
label="Min"
|
||||||
|
sortable
|
||||||
|
min-width="60"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="max"
|
||||||
|
label="Max"
|
||||||
|
sortable
|
||||||
|
min-width="60"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="median"
|
||||||
|
label="Med"
|
||||||
|
sortable
|
||||||
|
min-width="60"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="tp90"
|
||||||
|
label="90%"
|
||||||
|
sortable
|
||||||
|
min-width="60"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="tp95"
|
||||||
|
label="95%"
|
||||||
|
sortable
|
||||||
|
min-width="60"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="tp99"
|
||||||
|
label="99%"
|
||||||
|
sortable
|
||||||
|
min-width="60"
|
||||||
|
/>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="Throughput">
|
||||||
|
<el-table-column
|
||||||
|
prop="transactions"
|
||||||
|
label="Trans/s"
|
||||||
|
sortable
|
||||||
|
width="100"
|
||||||
|
/>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="NetWork(KB/sec)" align="center">
|
||||||
|
<el-table-column
|
||||||
|
prop="received"
|
||||||
|
label="Recd"
|
||||||
|
sortable
|
||||||
|
align="center"
|
||||||
|
width="100"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="sent"
|
||||||
|
label="Sent"
|
||||||
|
sortable
|
||||||
|
align="center"
|
||||||
|
width="100"
|
||||||
|
/>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "RequestStatistics",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tableData: [],
|
||||||
|
originalData: [],
|
||||||
|
id: '',
|
||||||
|
searchLabel: '',
|
||||||
|
showSearch: false,
|
||||||
|
showBtn: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: ['report', 'isShare', 'shareId', 'planReportTemplate'],
|
||||||
|
methods: {
|
||||||
|
initTableData() {
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
let data = this.planReportTemplate.reportStatistics;
|
||||||
|
this.tableData = data;
|
||||||
|
this.originalData = data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
click(column) {
|
||||||
|
this.searchLabel = '';
|
||||||
|
this.tableData = this.originalData;
|
||||||
|
this.$set(column, 'showSearch', !column.showSearch);
|
||||||
|
},
|
||||||
|
filterLabel() {
|
||||||
|
this.tableData = this.searchLabel ? this.originalData.filter(this.createFilter(this.searchLabel)) : this.originalData;
|
||||||
|
},
|
||||||
|
createFilter(queryString) {
|
||||||
|
return item => {
|
||||||
|
return (item.label.toLowerCase().indexOf(queryString.toLowerCase()) !== -1);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
report: {
|
||||||
|
handler(val) {
|
||||||
|
if (!val.status || !val.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let status = val.status;
|
||||||
|
this.id = val.id;
|
||||||
|
if (status === "Completed" || status === "Running") {
|
||||||
|
this.initTableData();
|
||||||
|
} else {
|
||||||
|
this.tableData = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
planReportTemplate: {
|
||||||
|
handler() {
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
this.initTableData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search_input :deep( .el-input__inner ) {
|
||||||
|
border-radius: 50px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,190 @@
|
||||||
|
<template>
|
||||||
|
<el-card>
|
||||||
|
<template v-slot:header>
|
||||||
|
<span class="title">Response Time</span>
|
||||||
|
</template>
|
||||||
|
<div v-for="(option, index) in responseTimeList" :key="index">
|
||||||
|
<ms-chart ref="chart1" :options="option" :autoresize="true"></ms-chart>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MsChart from "metersphere-frontend/src/components/chart/MsChart";
|
||||||
|
import {getPerformanceReportResChart} from "@/api/load-test";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ResponseTimeCompareCard",
|
||||||
|
components: {MsChart},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
responseTimeList: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initCard() {
|
||||||
|
this.responseTimeList = [];
|
||||||
|
this.reportId = this.$route.path.split('/')[4];
|
||||||
|
this.compareReports = JSON.parse(sessionStorage.getItem("compareReports"));
|
||||||
|
|
||||||
|
this.compareReports.forEach(report => {
|
||||||
|
this.initOverview(report);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
initOverview(report) {
|
||||||
|
getPerformanceReportResChart(report.id)
|
||||||
|
.then(({data}) => {
|
||||||
|
let yAxisList = data.filter(m => m.yAxis2 === -1).map(m => m.yAxis);
|
||||||
|
let yAxis2List = data.filter(m => m.yAxis === -1).map(m => m.yAxis2);
|
||||||
|
let yAxisListMax = this._getChartMax(yAxisList);
|
||||||
|
let yAxis2ListMax = this._getChartMax(yAxis2List);
|
||||||
|
|
||||||
|
let yAxisIndex0List = data.filter(m => m.yAxis2 === -1).map(m => m.groupName);
|
||||||
|
yAxisIndex0List = this._unique(yAxisIndex0List);
|
||||||
|
let yAxisIndex1List = data.filter(m => m.yAxis === -1).map(m => m.groupName);
|
||||||
|
yAxisIndex1List = this._unique(yAxisIndex1List);
|
||||||
|
|
||||||
|
let resOption = {
|
||||||
|
title: {
|
||||||
|
text: report.name + " " + this.$options.filters['datetimeFormat'](report.createTime),
|
||||||
|
left: 'center',
|
||||||
|
top: 20,
|
||||||
|
textStyle: {
|
||||||
|
color: '#99743C'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: 'axis',
|
||||||
|
// extraCssText: 'z-index: 999;',
|
||||||
|
confine: true,
|
||||||
|
formatter: function (params, ticket, callback) {
|
||||||
|
let result = "";
|
||||||
|
let name = params[0].name;
|
||||||
|
result += name + "<br/>";
|
||||||
|
for (let i = 0; i < params.length; i++) {
|
||||||
|
let seriesName = params[i].seriesName;
|
||||||
|
if (seriesName.length > 100) {
|
||||||
|
seriesName = seriesName.substring(0, 100);
|
||||||
|
}
|
||||||
|
let value = params[i].value;
|
||||||
|
let marker = params[i].marker;
|
||||||
|
result += marker + seriesName + ": " + value[1] + "<br/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
xAxis: {},
|
||||||
|
yAxis: [{
|
||||||
|
name: 'User',
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: yAxisListMax,
|
||||||
|
interval: yAxisListMax / 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Response Time',
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: yAxis2ListMax,
|
||||||
|
interval: yAxis2ListMax / 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: []
|
||||||
|
}
|
||||||
|
let setting = {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'users',
|
||||||
|
color: '#0CA74A',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
yAxisIndex0List.forEach(item => {
|
||||||
|
setting["series"].splice(0, 0, {name: item, yAxisIndex: '0'})
|
||||||
|
})
|
||||||
|
|
||||||
|
yAxisIndex1List.forEach(item => {
|
||||||
|
setting["series"].splice(0, 0, {name: item, yAxisIndex: '1'})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.responseTimeList.push(this.generateOption(resOption, data, setting));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.responseTimeList = [];
|
||||||
|
})
|
||||||
|
},
|
||||||
|
generateOption(option, data, setting) {
|
||||||
|
let chartData = data;
|
||||||
|
let seriesArray = [];
|
||||||
|
for (let set in setting) {
|
||||||
|
if (set === "series") {
|
||||||
|
seriesArray = setting[set];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.$set(option, set, setting[set]);
|
||||||
|
}
|
||||||
|
let legend = [], series = {}, xAxis = [], seriesData = [];
|
||||||
|
chartData.forEach(item => {
|
||||||
|
if (!xAxis.includes(item.xAxis)) {
|
||||||
|
xAxis.push(item.xAxis);
|
||||||
|
}
|
||||||
|
xAxis.sort()
|
||||||
|
let name = item.groupName
|
||||||
|
if (!legend.includes(name)) {
|
||||||
|
legend.push(name)
|
||||||
|
series[name] = []
|
||||||
|
}
|
||||||
|
if (item.yAxis === -1) {
|
||||||
|
series[name].splice(xAxis.indexOf(item.xAxis), 0, [item.xAxis, item.yAxis2.toFixed(2)]);
|
||||||
|
} else {
|
||||||
|
series[name].splice(xAxis.indexOf(item.xAxis), 0, [item.xAxis, item.yAxis.toFixed(2)]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.$set(option.legend, "data", legend);
|
||||||
|
this.$set(option.legend, "type", "scroll");
|
||||||
|
this.$set(option.legend, "bottom", "10px");
|
||||||
|
this.$set(option.xAxis, "data", xAxis);
|
||||||
|
for (let name in series) {
|
||||||
|
let d = series[name];
|
||||||
|
d.sort((a, b) => a[0].localeCompare(b[0]));
|
||||||
|
let items = {
|
||||||
|
name: name,
|
||||||
|
type: 'line',
|
||||||
|
data: d,
|
||||||
|
smooth: true
|
||||||
|
};
|
||||||
|
let seriesArrayNames = seriesArray.map(m => m.name);
|
||||||
|
if (seriesArrayNames.includes(name)) {
|
||||||
|
for (let j = 0; j < seriesArray.length; j++) {
|
||||||
|
let seriesObj = seriesArray[j];
|
||||||
|
if (seriesObj['name'] === name) {
|
||||||
|
Object.assign(items, seriesObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seriesData.push(items);
|
||||||
|
}
|
||||||
|
this.$set(option, "series", seriesData);
|
||||||
|
return option;
|
||||||
|
},
|
||||||
|
_getChartMax(arr) {
|
||||||
|
const max = Math.max(...arr);
|
||||||
|
return Math.ceil(max / 4.5) * 5;
|
||||||
|
},
|
||||||
|
_unique(arr) {
|
||||||
|
return Array.from(new Set(arr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.echarts {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,128 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog :close-on-click-modal="false"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
:title="$t('load_test.completed_test_report')" width="60%"
|
||||||
|
v-loading="loading"
|
||||||
|
:show-close="false"
|
||||||
|
:visible.sync="loadReportVisible">
|
||||||
|
|
||||||
|
<el-header class="header-btn">
|
||||||
|
<ms-dialog-header :enable-cancel="true" @cancel="close" @confirm="handleCompare" btn-size="mini">
|
||||||
|
</ms-dialog-header>
|
||||||
|
</el-header>
|
||||||
|
|
||||||
|
<ms-table
|
||||||
|
:data="tableData"
|
||||||
|
:show-select-all="false"
|
||||||
|
:screen-height="screenHeight"
|
||||||
|
ref="table"
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
prop="name"
|
||||||
|
:label="$t('commons.name')"
|
||||||
|
show-overflow-tooltip>
|
||||||
|
<template v-slot:default="scope">
|
||||||
|
<i v-if="scope.row.id === report.id" class="el-icon-star-on"></i> {{ scope.row.name }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="userName"
|
||||||
|
:label="$t('report.user_name')"
|
||||||
|
show-overflow-tooltip>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="triggerMode"
|
||||||
|
:label="$t('test_track.report.list.trigger_mode')">
|
||||||
|
<template v-slot:default="scope">
|
||||||
|
<report-trigger-mode-item :trigger-mode="scope.row.triggerMode"/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
:label="$t('commons.create_time')">
|
||||||
|
<template v-slot:default="scope">
|
||||||
|
<i class="el-icon-time"/>
|
||||||
|
<span class="last-modified">{{ scope.row.createTime | datetimeFormat }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</ms-table>
|
||||||
|
<ms-table-pagination :change="getCompareReports" :current-page.sync="currentPage" :page-size.sync="pageSize"
|
||||||
|
:total="total"/>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MsTablePagination from "metersphere-frontend/src/components/pagination/TablePagination";
|
||||||
|
import MsDialogFooter from "metersphere-frontend/src/components/MsDialogFooter";
|
||||||
|
import ReportTriggerModeItem from "metersphere-frontend/src/components/tableItem/ReportTriggerModeItem";
|
||||||
|
import MsTable from "metersphere-frontend/src/components/table/MsTable";
|
||||||
|
import MsDialogHeader from "metersphere-frontend/src/components/MsDialogHeader";
|
||||||
|
import {searchReports} from "@/api/report";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "SameTestReports",
|
||||||
|
components: {MsDialogHeader, MsTable, ReportTriggerModeItem, MsDialogFooter, MsTablePagination},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loadReportVisible: false,
|
||||||
|
loading: false,
|
||||||
|
tableData: [],
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
report: {},
|
||||||
|
compareReports: [],
|
||||||
|
screenHeight: 'calc(100vh - 400px)',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open(report) {
|
||||||
|
this.report = report;
|
||||||
|
this.compareReports = [];
|
||||||
|
this.getCompareReports(report);
|
||||||
|
|
||||||
|
this.compareReports.push(report);
|
||||||
|
|
||||||
|
this.loadReportVisible = true;
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.loadReportVisible = false;
|
||||||
|
},
|
||||||
|
getCompareReports() {
|
||||||
|
let condition = {
|
||||||
|
testId: this.report.testId,
|
||||||
|
filters: {status: ["Completed"]}
|
||||||
|
};
|
||||||
|
this.loading = searchReports(this.currentPage, this.pageSize, condition)
|
||||||
|
.then(res => {
|
||||||
|
let data = res.data;
|
||||||
|
this.total = data.itemCount;
|
||||||
|
this.tableData = data.listObject;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleCompare() {
|
||||||
|
|
||||||
|
let reportIds = [...this.$refs.table.selectIds];
|
||||||
|
this.tableData
|
||||||
|
.filter(r => reportIds.indexOf(r.id) > -1 && this.report.id !== r.id)
|
||||||
|
.forEach(r => this.compareReports.push(r));
|
||||||
|
|
||||||
|
sessionStorage.setItem("compareReports", JSON.stringify(this.compareReports));
|
||||||
|
this.close();
|
||||||
|
this.$router.push({path: '/performance/report/compare/' + reportIds[0]});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.header-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: 0 0;
|
||||||
|
border: none;
|
||||||
|
outline: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,50 @@
|
||||||
|
<template>
|
||||||
|
<el-tabs>
|
||||||
|
<el-tab-pane :label="$t('load_test.pressure_config')">
|
||||||
|
<performance-pressure-config :is-read-only="true" :test="test" :report="report" :report-id="reportId"
|
||||||
|
:is-share="isShare" :share-id="shareId" @fileChange="fileChange"/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane :label="$t('load_test.advanced_config')">
|
||||||
|
<performance-advanced-config :is-read-only="true" :report-id="reportId" :report="report" :is-share="isShare"
|
||||||
|
:share-id="shareId" ref="advancedConfig"/>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import PerformancePressureConfig from "../../../load/PerformancePressureConfig";
|
||||||
|
import PerformanceAdvancedConfig from "../../../load/PerformanceAdvancedConfig";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "TestConfiguration",
|
||||||
|
components: {PerformancePressureConfig, PerformanceAdvancedConfig},
|
||||||
|
props: {
|
||||||
|
test: Object,
|
||||||
|
testId: String,
|
||||||
|
reportId: String,
|
||||||
|
report: Object,
|
||||||
|
isShare: Boolean,
|
||||||
|
shareId: String,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fileChange(threadGroups) {
|
||||||
|
let csvSet = new Set;
|
||||||
|
threadGroups.forEach(tg => {
|
||||||
|
if (tg.csvFiles) {
|
||||||
|
tg.csvFiles.map(item => csvSet.add(item));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let csvFiles = [];
|
||||||
|
for (const f of csvSet) {
|
||||||
|
csvFiles.push({name: f, csvSplit: false, csvHasHeader: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs.advancedConfig.csvFiles = csvFiles;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,728 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div style="padding-bottom: 5px;">
|
||||||
|
<el-link type="primary" @click="resetDefault()">{{ $t('load_test.report.set_default') }}</el-link>
|
||||||
|
</div>
|
||||||
|
<el-collapse v-model="activeNames" class="test-detail">
|
||||||
|
<el-collapse-item name="users">
|
||||||
|
<template v-slot:title>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<span>{{ $t('load_test.report.ActiveThreadsChart') }}</span>
|
||||||
|
<span style="float:right;">
|
||||||
|
<el-link type="primary" @click="selectAll( 'ActiveThreadsChart', $event)">
|
||||||
|
{{ $t('load_test.report.select_all') }}
|
||||||
|
</el-link>
|
||||||
|
/
|
||||||
|
<el-link type="default" @click="unselectAll('ActiveThreadsChart', $event)">
|
||||||
|
{{ $t('load_test.report.unselect_all') }}
|
||||||
|
</el-link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-checkbox-group v-model="checkList['ActiveThreadsChart']"
|
||||||
|
@change="handleChecked('ActiveThreadsChart')">
|
||||||
|
<div v-for="name in checkOptions['ActiveThreadsChart']" :key="name">
|
||||||
|
<el-tooltip class="item" effect="dark"
|
||||||
|
:content="name"
|
||||||
|
:disabled="name.length < minLength"
|
||||||
|
placement="top">
|
||||||
|
<el-checkbox :label="name"/>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</el-collapse-item>
|
||||||
|
<el-collapse-item name="transactions">
|
||||||
|
<template v-slot:title>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<span>{{ $t('load_test.report.TransactionsChart') }}</span>
|
||||||
|
<span style="float:right;">
|
||||||
|
<el-link type="primary" @click="selectAll( 'TransactionsChart', $event)">
|
||||||
|
{{ $t('load_test.report.select_all') }}
|
||||||
|
</el-link>
|
||||||
|
/
|
||||||
|
<el-link type="default" @click="unselectAll('TransactionsChart', $event)">
|
||||||
|
{{ $t('load_test.report.unselect_all') }}
|
||||||
|
</el-link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-checkbox-group v-model="checkList['TransactionsChart']" @change="handleChecked('TransactionsChart')">
|
||||||
|
<div v-for="name in checkOptions['TransactionsChart']"
|
||||||
|
:key="name">
|
||||||
|
<el-tooltip class="item" effect="dark"
|
||||||
|
|
||||||
|
:content="name"
|
||||||
|
:disabled="name.length < minLength"
|
||||||
|
placement="top">
|
||||||
|
<el-checkbox :label="name"/>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</el-collapse-item>
|
||||||
|
<el-collapse-item name="responseTime">
|
||||||
|
<template v-slot:title>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<span>{{ $t('load_test.report.ResponseTimeChart') }}</span>
|
||||||
|
<span style="float:right;">
|
||||||
|
<el-link type="primary" @click="selectAll( 'ResponseTimeChart', $event)">
|
||||||
|
{{ $t('load_test.report.select_all') }}
|
||||||
|
</el-link>
|
||||||
|
/
|
||||||
|
<el-link type="default" @click="unselectAll('ResponseTimeChart', $event)">
|
||||||
|
{{ $t('load_test.report.unselect_all') }}
|
||||||
|
</el-link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-checkbox-group v-model="checkList['ResponseTimeChart']" @change="handleChecked('ResponseTimeChart')">
|
||||||
|
<div v-for="name in checkOptions['ResponseTimeChart']"
|
||||||
|
:key="name">
|
||||||
|
<el-tooltip class="item" effect="dark"
|
||||||
|
:content="name"
|
||||||
|
:disabled="name.length < minLength"
|
||||||
|
placement="top">
|
||||||
|
<el-checkbox :label="name"/>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</el-collapse-item>
|
||||||
|
<el-collapse-item name="responseTimePercentiles">
|
||||||
|
<template v-slot:title>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<span>{{ $t('load_test.report.ResponseTimePercentilesChart') }}</span>
|
||||||
|
<span style="float:right;">
|
||||||
|
<el-link type="primary" @click="selectAll( 'ResponseTimePercentilesChart', $event)">
|
||||||
|
{{ $t('load_test.report.select_all') }}
|
||||||
|
</el-link>
|
||||||
|
/
|
||||||
|
<el-link type="default" @click="unselectAll('ResponseTimePercentilesChart', $event)">
|
||||||
|
{{ $t('load_test.report.unselect_all') }}
|
||||||
|
</el-link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-checkbox-group v-model="checkList['ResponseTimePercentilesChart']"
|
||||||
|
@change="handleChecked('ResponseTimePercentilesChart')">
|
||||||
|
<div v-for="name in checkOptions['ResponseTimePercentilesChart']"
|
||||||
|
:key="name">
|
||||||
|
<el-tooltip class="item" effect="dark"
|
||||||
|
:content="name"
|
||||||
|
:disabled="name.length < minLength"
|
||||||
|
placement="top">
|
||||||
|
<el-checkbox :label="name"/>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</el-collapse-item>
|
||||||
|
<el-collapse-item :title="$t('load_test.report.ResponseCodeChart')" name="responseCode">
|
||||||
|
<template v-slot:title>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<span>{{ $t('load_test.report.ResponseCodeChart') }}</span>
|
||||||
|
<span style="float:right;">
|
||||||
|
<el-link type="primary" @click="selectAll( 'ResponseCodeChart', $event)">
|
||||||
|
{{ $t('load_test.report.select_all') }}
|
||||||
|
</el-link>
|
||||||
|
/
|
||||||
|
<el-link type="default" @click="unselectAll('ResponseCodeChart', $event)">
|
||||||
|
{{ $t('load_test.report.unselect_all') }}
|
||||||
|
</el-link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-checkbox-group v-model="checkList['ResponseCodeChart']" @change="handleChecked('ResponseCodeChart')">
|
||||||
|
<div v-for="name in checkOptions['ResponseCodeChart']"
|
||||||
|
:key="name">
|
||||||
|
<el-tooltip class="item" effect="dark"
|
||||||
|
:content="name"
|
||||||
|
:disabled="name.length < minLength"
|
||||||
|
placement="top">
|
||||||
|
<el-checkbox :label="name"/>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</el-collapse-item>
|
||||||
|
<el-collapse-item :title="$t('load_test.report.LatencyChart')" name="latency">
|
||||||
|
<template v-slot:title>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<span>{{ $t('load_test.report.LatencyChart') }}</span>
|
||||||
|
<span style="float:right;">
|
||||||
|
<el-link type="primary" @click="selectAll( 'LatencyChart', $event)">
|
||||||
|
{{ $t('load_test.report.select_all') }}
|
||||||
|
</el-link>
|
||||||
|
/
|
||||||
|
<el-link type="default" @click="unselectAll('LatencyChart', $event)">
|
||||||
|
{{ $t('load_test.report.unselect_all') }}
|
||||||
|
</el-link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-checkbox-group v-model="checkList['LatencyChart']" @change="handleChecked('LatencyChart')">
|
||||||
|
<div v-for="name in checkOptions['LatencyChart']"
|
||||||
|
:key="name">
|
||||||
|
<el-tooltip class="item" effect="dark"
|
||||||
|
:content="name"
|
||||||
|
:disabled="name.length < minLength"
|
||||||
|
placement="top">
|
||||||
|
<el-checkbox :label="name"/>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</el-collapse-item>
|
||||||
|
<el-collapse-item :title="$t('load_test.report.BytesThroughputChart')" name="bytes">
|
||||||
|
<template v-slot:title>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<span>{{ $t('load_test.report.BytesThroughputChart') }}</span>
|
||||||
|
<span style="float:right;">
|
||||||
|
<el-link type="primary" @click="selectAll( 'BytesThroughputChart', $event)">
|
||||||
|
{{ $t('load_test.report.select_all') }}
|
||||||
|
</el-link>
|
||||||
|
/
|
||||||
|
<el-link type="default" @click="unselectAll('BytesThroughputChart', $event)">
|
||||||
|
{{ $t('load_test.report.unselect_all') }}
|
||||||
|
</el-link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-checkbox-group v-model="checkList['BytesThroughputChart']"
|
||||||
|
@change="handleChecked('BytesThroughputChart')">
|
||||||
|
<div v-for="name in checkOptions['BytesThroughputChart']"
|
||||||
|
:key="name">
|
||||||
|
<el-tooltip class="item" effect="dark"
|
||||||
|
:content="name"
|
||||||
|
:disabled="name.length < minLength"
|
||||||
|
placement="top">
|
||||||
|
<el-checkbox :label="name"/>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</el-collapse-item>
|
||||||
|
<el-collapse-item :title="$t('load_test.report.ErrorsChart')" name="errors">
|
||||||
|
<template v-slot:title>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<span>{{ $t('load_test.report.ErrorsChart') }}</span>
|
||||||
|
<span style="float:right;">
|
||||||
|
<el-link type="primary" @click="selectAll( 'ErrorsChart', $event)">
|
||||||
|
{{ $t('load_test.report.select_all') }}
|
||||||
|
</el-link>
|
||||||
|
/
|
||||||
|
<el-link type="default" @click="unselectAll('ErrorsChart', $event)">
|
||||||
|
{{ $t('load_test.report.unselect_all') }}
|
||||||
|
</el-link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-checkbox-group v-model="checkList['ErrorsChart']" @change="handleChecked('ErrorsChart')">
|
||||||
|
<div v-for="name in checkOptions['ErrorsChart']"
|
||||||
|
:key="name">
|
||||||
|
<el-tooltip class="item" effect="dark"
|
||||||
|
:content="name"
|
||||||
|
:disabled="name.length < minLength"
|
||||||
|
placement="top">
|
||||||
|
<el-checkbox :label="name"/>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="18" v-loading="result.loading">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<ms-chart ref="chart2"
|
||||||
|
v-if="refresh"
|
||||||
|
class="chart-config"
|
||||||
|
:options="totalOption"
|
||||||
|
@datazoom="changeDataZoom"
|
||||||
|
:autoresize="true"/>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<el-col :offset="2" :span="20">
|
||||||
|
<el-table
|
||||||
|
v-if="refresh"
|
||||||
|
:data="tableData"
|
||||||
|
stripe
|
||||||
|
border
|
||||||
|
style="width: 100%">
|
||||||
|
<el-table-column label="Label" align="center">
|
||||||
|
<el-table-column
|
||||||
|
prop="label"
|
||||||
|
label="Label"
|
||||||
|
sortable>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Aggregate" align="center">
|
||||||
|
<el-table-column
|
||||||
|
prop="avg"
|
||||||
|
label="Avg."
|
||||||
|
width="100"
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="min"
|
||||||
|
label="Min."
|
||||||
|
width="100"
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="max"
|
||||||
|
label="Max."
|
||||||
|
width="100"
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Range" align="center">
|
||||||
|
<el-table-column
|
||||||
|
prop="startTime"
|
||||||
|
label="Start"
|
||||||
|
width="160"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="endTime"
|
||||||
|
label="End"
|
||||||
|
width="160"
|
||||||
|
/>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MsChart from "metersphere-frontend/src/components/chart/MsChart";
|
||||||
|
|
||||||
|
const color = ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'];
|
||||||
|
|
||||||
|
const groupBy = function (xs, key) {
|
||||||
|
return xs.reduce(function (rv, x) {
|
||||||
|
(rv[x[key]] = rv[x[key]] || []).push(x);
|
||||||
|
return rv;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const CHART_MAP = [
|
||||||
|
'ActiveThreadsChart',
|
||||||
|
'TransactionsChart',
|
||||||
|
'ResponseTimeChart',
|
||||||
|
'ResponseTimePercentilesChart',
|
||||||
|
'ResponseCodeChart',
|
||||||
|
'ErrorsChart',
|
||||||
|
'LatencyChart',
|
||||||
|
'BytesThroughputChart',
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "TestDetails",
|
||||||
|
components: {MsChart},
|
||||||
|
props: ['report', 'export', 'isShare', 'shareId', 'planReportTemplate'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
result: {},
|
||||||
|
activeNames: 'users',
|
||||||
|
minLength: 35,
|
||||||
|
loadOption: {},
|
||||||
|
resOption: {},
|
||||||
|
totalOption: {},
|
||||||
|
responseCodes: [],
|
||||||
|
checkList: CHART_MAP.reduce((result, curr) => {
|
||||||
|
result[curr] = [];
|
||||||
|
return result;
|
||||||
|
}, {}),
|
||||||
|
checkOptions: {},
|
||||||
|
defaultProps: {
|
||||||
|
children: 'children',
|
||||||
|
label: 'label'
|
||||||
|
},
|
||||||
|
init: false,
|
||||||
|
refresh: true,
|
||||||
|
tableData: [],
|
||||||
|
baseOption: {
|
||||||
|
color: color,
|
||||||
|
grid: {
|
||||||
|
// right: '35%' // 动态改这个值
|
||||||
|
},
|
||||||
|
title: {},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross'
|
||||||
|
},
|
||||||
|
confine: true,
|
||||||
|
formatter: function (params, ticket, callback) {
|
||||||
|
let result = "";
|
||||||
|
let name = params[0].name;
|
||||||
|
result += name + "<br/>";
|
||||||
|
for (let i = 0; i < params.length; i++) {
|
||||||
|
let seriesName = params[i].seriesName;
|
||||||
|
if (seriesName.length > 100) {
|
||||||
|
seriesName = seriesName.substring(0, 100);
|
||||||
|
}
|
||||||
|
let value = params[i].value;
|
||||||
|
let marker = params[i].marker;
|
||||||
|
result += marker + seriesName + ": " + value[1] + "<br/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
y: 'top',
|
||||||
|
},
|
||||||
|
xAxis: {boundaryGap: false},
|
||||||
|
yAxis: [],
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: 0,
|
||||||
|
end: 20
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: []
|
||||||
|
},
|
||||||
|
seriesData: [],
|
||||||
|
legend: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resetDefault() {
|
||||||
|
|
||||||
|
this.checkList['ActiveThreadsChart'] = ['ALL'];
|
||||||
|
this.checkList['TransactionsChart'] = ['ALL'];
|
||||||
|
this.checkList['ResponseTimeChart'] = ['ALL'];
|
||||||
|
//
|
||||||
|
this.checkList['ResponseTimePercentilesChart'] = [];
|
||||||
|
this.checkList['ErrorsChart'] = [];
|
||||||
|
this.checkList['LatencyChart'] = [];
|
||||||
|
this.checkList['BytesThroughputChart'] = [];
|
||||||
|
|
||||||
|
this.getTotalChart();
|
||||||
|
},
|
||||||
|
selectAll(name, e) {
|
||||||
|
if (e) {
|
||||||
|
e.stopPropagation(); // 阻止冒泡
|
||||||
|
}
|
||||||
|
this.seriesData = [];
|
||||||
|
this.totalOption = {};
|
||||||
|
this.baseOption.yAxis = [];
|
||||||
|
this.legend = [];
|
||||||
|
this.checkList[name] = this.checkOptions[name];
|
||||||
|
|
||||||
|
this.getTotalChart();
|
||||||
|
},
|
||||||
|
unselectAll(name, e) {
|
||||||
|
if (e) {
|
||||||
|
e.stopPropagation(); // 阻止冒泡
|
||||||
|
}
|
||||||
|
this.seriesData = [];
|
||||||
|
this.totalOption = {};
|
||||||
|
this.baseOption.yAxis = [];
|
||||||
|
this.legend = [];
|
||||||
|
if (name) {
|
||||||
|
this.checkList[name] = [];
|
||||||
|
this.getTotalChart();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const name in this.checkList) {
|
||||||
|
this.checkList[name] = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleChecked(name) {
|
||||||
|
|
||||||
|
this.getTotalChart();
|
||||||
|
|
||||||
|
this.refresh = false;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.refresh = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
initTableData() {
|
||||||
|
for (const name of CHART_MAP) {
|
||||||
|
this.getCheckOptions(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resetDefault();
|
||||||
|
},
|
||||||
|
getCheckOptions(reportKey) {
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
let data = this.planReportTemplate.checkOptions[reportKey];
|
||||||
|
this.handleGetCheckOptions(data, reportKey);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleGetCheckOptions(data, reportKey) {
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
this.init = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let yAxisIndex0List = data.filter(m => m.yAxis2 === -1).map(m => m.groupName);
|
||||||
|
yAxisIndex0List = this._unique(yAxisIndex0List);
|
||||||
|
this.checkOptions[reportKey] = ['ALL'].concat(yAxisIndex0List);
|
||||||
|
},
|
||||||
|
getTotalChart() {
|
||||||
|
this.result.loading = true;
|
||||||
|
|
||||||
|
this.totalOption = {};
|
||||||
|
this.seriesData = [];
|
||||||
|
this.baseOption.yAxis = [];
|
||||||
|
this.legend = [];
|
||||||
|
let promises = [];
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
let chars = [];
|
||||||
|
for (let name in this.checkList) {
|
||||||
|
let data = this.planReportTemplate.checkOptions[name];
|
||||||
|
chars.push({data, 'reportKey': name});
|
||||||
|
}
|
||||||
|
this.handleGetTotalChart(chars);
|
||||||
|
} else {
|
||||||
|
for (let name in this.checkList) {
|
||||||
|
promises.push(this.getChart(name, this.checkList[name]));
|
||||||
|
}
|
||||||
|
Promise.all(promises).then((res) => {
|
||||||
|
this.handleGetTotalChart(res);
|
||||||
|
}).catch(() => {
|
||||||
|
this.result.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleGetTotalChart(res) {
|
||||||
|
res = res.filter(v => !!v);
|
||||||
|
if (res.length === 0) {
|
||||||
|
this.refresh = false;
|
||||||
|
this.result.loading = false;
|
||||||
|
} else {
|
||||||
|
this.refresh = true;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < res.length; i++) {
|
||||||
|
if (i === 0) {
|
||||||
|
this.baseOption.yAxis.push({
|
||||||
|
name: this.$t('load_test.report.' + res[i].reportKey),
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
position: 'left',
|
||||||
|
boundaryGap: [0, '100%']
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.baseOption.yAxis.push({
|
||||||
|
name: this.$t('load_test.report.' + res[i].reportKey),
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
position: 'right',
|
||||||
|
nameRotate: 20,
|
||||||
|
offset: (i - 1) * 50,
|
||||||
|
boundaryGap: [0, '100%']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.totalOption = this.generateOption(this.baseOption, res[i].data, i);
|
||||||
|
}
|
||||||
|
this.totalOption.grid.right = (res.length - 1) * 5 + '%';
|
||||||
|
this.changeDataZoom({start: 0, end: 100});
|
||||||
|
this.result.loading = false;
|
||||||
|
},
|
||||||
|
getChart(reportKey, checkList) {
|
||||||
|
if (!checkList || checkList.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.totalOption = {};
|
||||||
|
},
|
||||||
|
handleGetChart(data, reportKey, checkList) {
|
||||||
|
let allData = [];
|
||||||
|
let checkAllOption = checkList.indexOf('ALL') > -1;
|
||||||
|
if (checkAllOption) {
|
||||||
|
let avgOpt = [
|
||||||
|
'ResponseTimeChart',
|
||||||
|
'ResponseTimePercentilesChart',
|
||||||
|
'LatencyChart',
|
||||||
|
];
|
||||||
|
let result = groupBy(data, 'xAxis');
|
||||||
|
for (const xAxis in result) {
|
||||||
|
let yAxis = result[xAxis].map(a => a.yAxis).reduce((a, b) => a + b, 0);
|
||||||
|
if (avgOpt.indexOf(reportKey) > -1) {
|
||||||
|
yAxis = yAxis / result[xAxis].length;
|
||||||
|
}
|
||||||
|
allData.push({
|
||||||
|
groupName: 'ALL',
|
||||||
|
xAxis: xAxis,
|
||||||
|
yAxis: yAxis
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
data = data.filter(item => {
|
||||||
|
if (checkList.indexOf(item.groupName) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 选中了all
|
||||||
|
data = data.concat(allData);
|
||||||
|
|
||||||
|
|
||||||
|
// prefix
|
||||||
|
data.forEach(item => {
|
||||||
|
item.groupName = this.$t('load_test.report.' + reportKey) + ': ' + item.groupName;
|
||||||
|
});
|
||||||
|
return {data, reportKey};
|
||||||
|
},
|
||||||
|
generateOption(option, data, yAxisIndex) {
|
||||||
|
let chartData = data;
|
||||||
|
let series = {}, xAxis = [];
|
||||||
|
chartData.forEach(item => {
|
||||||
|
if (!xAxis.includes(item.xAxis)) {
|
||||||
|
xAxis.push(item.xAxis);
|
||||||
|
}
|
||||||
|
xAxis.sort();
|
||||||
|
let name = item.groupName;
|
||||||
|
if (!this.legend.includes(name)) {
|
||||||
|
this.legend.push(name);
|
||||||
|
series[name] = [];
|
||||||
|
}
|
||||||
|
if (series[name]) {
|
||||||
|
series[name].splice(xAxis.indexOf(item.xAxis), 0, [item.xAxis, item.yAxis.toFixed(2)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
this.$set(option.legend, "data", this.legend);
|
||||||
|
this.$set(option.legend, "type", "scroll");
|
||||||
|
this.$set(option.legend, "bottom", "10px");
|
||||||
|
this.$set(option.xAxis, "data", xAxis);
|
||||||
|
for (let name in series) {
|
||||||
|
let d = series[name];
|
||||||
|
d.sort((a, b) => a[0].localeCompare(b[0]));
|
||||||
|
let items = {
|
||||||
|
name: name,
|
||||||
|
type: 'line',
|
||||||
|
data: d,
|
||||||
|
yAxisIndex: yAxisIndex,
|
||||||
|
smooth: true,
|
||||||
|
sampling: 'lttb',
|
||||||
|
showSymbol: false,
|
||||||
|
animation: !this.export,
|
||||||
|
};
|
||||||
|
this.seriesData.push(items);
|
||||||
|
}
|
||||||
|
this.$set(option, "series", this.seriesData);
|
||||||
|
return option;
|
||||||
|
},
|
||||||
|
changeDataZoom(params) {
|
||||||
|
let start = params.start / 100;
|
||||||
|
let end = params.end / 100;
|
||||||
|
if (params.batch) {
|
||||||
|
start = params.batch[0].start / 100;
|
||||||
|
end = params.batch[0].end / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tableData = [];
|
||||||
|
for (let i = 0; i < this.seriesData.length; i++) {
|
||||||
|
let sub = this.seriesData[i].data, label = this.seriesData[i].name;
|
||||||
|
let len = 0;
|
||||||
|
let min, avg, max, sum = 0, startTime, endTime;
|
||||||
|
for (let j = 0; j < sub.length; j++) {
|
||||||
|
let time = sub[j][0];
|
||||||
|
let value = Number.parseFloat(sub[j][1]);
|
||||||
|
let index = (j / (sub.length - 1)).toFixed(2);
|
||||||
|
if (index < start) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (index >= end) {
|
||||||
|
endTime = time;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startTime) {
|
||||||
|
startTime = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!min && !max) {
|
||||||
|
min = max = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (min > value) {
|
||||||
|
min = value;
|
||||||
|
}
|
||||||
|
if (max < value) {
|
||||||
|
max = value;
|
||||||
|
}
|
||||||
|
sum += value;
|
||||||
|
|
||||||
|
len++; // 实际 len
|
||||||
|
}
|
||||||
|
|
||||||
|
avg = (sum / len).toFixed(2);
|
||||||
|
tableData.push({label, min, max, avg, startTime, endTime});
|
||||||
|
}
|
||||||
|
this.tableData = tableData;
|
||||||
|
},
|
||||||
|
_getChartMax(arr) {
|
||||||
|
const max = Math.max(...arr);
|
||||||
|
return Math.ceil(max / 4.5) * 5;
|
||||||
|
},
|
||||||
|
_unique(arr) {
|
||||||
|
return Array.from(new Set(arr));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'$route'(to) {
|
||||||
|
if (to.name === "perReportView") {
|
||||||
|
this.id = to.path.split('/')[4];
|
||||||
|
this.init = false;
|
||||||
|
this.initTableData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
report: {
|
||||||
|
handler(val) {
|
||||||
|
if (!val.status || !val.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let status = val.status;
|
||||||
|
this.id = val.id;
|
||||||
|
if (this.init) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (status === "Running") {
|
||||||
|
this.getTotalChart();
|
||||||
|
} else if (status === "Completed") {
|
||||||
|
this.initTableData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
planReportTemplate: {
|
||||||
|
handler() {
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
this.initTableData();
|
||||||
|
// todo 勾选无效
|
||||||
|
// this.getTotalChart();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chart-config {
|
||||||
|
width: 100%;
|
||||||
|
height: 445px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-detail {
|
||||||
|
height: calc(100vh - 215px);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-checkbox__label ) {
|
||||||
|
font-size: 10px !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,584 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-row :gutter="12">
|
||||||
|
<el-col :span="4">
|
||||||
|
<el-card shadow="always" class="ms-card-index-1">
|
||||||
|
<span class="ms-card-data">
|
||||||
|
<span class="ms-card-data-digital">{{ maxUsers }}</span>
|
||||||
|
<span class="ms-card-data-unit"> VU</span>
|
||||||
|
</span>
|
||||||
|
<span class="ms-card-desc">{{ $t('load_test.report.ActiveThreadsChart') }}</span>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="4">
|
||||||
|
<el-card shadow="always" class="ms-card-index-2">
|
||||||
|
<span class="ms-card-data">
|
||||||
|
<span class="ms-card-data-digital">{{ avgTransactions }}</span>
|
||||||
|
<span class="ms-card-data-unit"> TPS</span>
|
||||||
|
</span>
|
||||||
|
<span class="ms-card-desc">{{ $t('load_test.report.TransactionsChart') }}</span>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="4">
|
||||||
|
<el-card shadow="always" class="ms-card-index-3">
|
||||||
|
<span class="ms-card-data">
|
||||||
|
<span class="ms-card-data-digital">{{ errors }}</span>
|
||||||
|
<span class="ms-card-data-unit"> %</span>
|
||||||
|
</span>
|
||||||
|
<span class="ms-card-desc">{{ $t('load_test.report.ErrorsChart') }}</span>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="4">
|
||||||
|
<el-card shadow="always" class="ms-card-index-4">
|
||||||
|
<span class="ms-card-data">
|
||||||
|
<span class="ms-card-data-digital">{{ avgResponseTime }}</span>
|
||||||
|
<span class="ms-card-data-unit"> s</span>
|
||||||
|
</span>
|
||||||
|
<span class="ms-card-desc">{{ $t('load_test.report.ResponseTimeChart') }}</span>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="4">
|
||||||
|
<el-card shadow="always" class="ms-card-index-5">
|
||||||
|
<span class="ms-card-data">
|
||||||
|
<span class="ms-card-data-digital">{{ responseTime90 }}</span>
|
||||||
|
<span class="ms-card-data-unit"> s</span>
|
||||||
|
</span>
|
||||||
|
<span class="ms-card-desc">90% {{ $t('load_test.report.ResponseTimeChart') }}</span>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="4">
|
||||||
|
<el-card shadow="always" class="ms-card-index-6">
|
||||||
|
<span class="ms-card-data">
|
||||||
|
<span class="ms-card-data-digital">{{ avgBandwidth }}</span>
|
||||||
|
<span class="ms-card-data-unit"> KiB/s</span>
|
||||||
|
</span>
|
||||||
|
<span class="ms-card-desc">{{ $t('load_test.report.Network') }}</span>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<ms-chart ref="chart1" :options="loadOption" class="chart-config" :autoresize="true"></ms-chart>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<ms-chart ref="chart2" :options="resOption" class="chart-config" :autoresize="true"></ms-chart>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MsChart from "metersphere-frontend/src/components/chart/MsChart";
|
||||||
|
|
||||||
|
const color = ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'];
|
||||||
|
|
||||||
|
const groupBy = function (xs, key) {
|
||||||
|
return xs.reduce(function (rv, x) {
|
||||||
|
(rv[x[key]] = rv[x[key]] || []).push(x);
|
||||||
|
return rv;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "TestOverview",
|
||||||
|
components: {MsChart},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
maxUsers: "0",
|
||||||
|
avgThroughput: "0",
|
||||||
|
avgTransactions: "0",
|
||||||
|
errors: "0",
|
||||||
|
avgResponseTime: "0",
|
||||||
|
responseTime90: "0",
|
||||||
|
avgBandwidth: "0",
|
||||||
|
loadOption: {},
|
||||||
|
resOption: {},
|
||||||
|
errorOption: {},
|
||||||
|
resCodeOption: {},
|
||||||
|
id: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: ['report', 'export', 'isShare', 'shareId', 'planReportTemplate'],
|
||||||
|
methods: {
|
||||||
|
initTableData() {
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
let data = this.planReportTemplate.testOverview;
|
||||||
|
this.buildInfo(data);
|
||||||
|
}
|
||||||
|
this.getLoadChart();
|
||||||
|
this.getResChart();
|
||||||
|
},
|
||||||
|
buildInfo(data) {
|
||||||
|
this.maxUsers = data ? data.maxUsers : '0';
|
||||||
|
this.avgThroughput = data ? data.avgThroughput : '0';
|
||||||
|
this.avgTransactions = data ? data.avgTransactions : '0';
|
||||||
|
this.errors = data ? data.errors : '0';
|
||||||
|
this.avgResponseTime = data ? data.avgResponseTime : '0';
|
||||||
|
this.responseTime90 = data ? data.responseTime90 : '0';
|
||||||
|
this.avgBandwidth = data ? data.avgBandwidth : '0';
|
||||||
|
},
|
||||||
|
getLoadChart() {
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
let data = this.planReportTemplate.loadChartData;
|
||||||
|
this.handleGetLoadChart(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleGetLoadChart(data) {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let loadOption = {
|
||||||
|
color: color,
|
||||||
|
title: {
|
||||||
|
text: 'Load',
|
||||||
|
left: 'center',
|
||||||
|
top: 20,
|
||||||
|
textStyle: {
|
||||||
|
color: '#65A2FF'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: 'axis',
|
||||||
|
// extraCssText: 'z-index: 999;',
|
||||||
|
confine: true,
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
xAxis: {},
|
||||||
|
series: []
|
||||||
|
};
|
||||||
|
|
||||||
|
let allData = [];
|
||||||
|
let result = groupBy(data, 'xAxis');
|
||||||
|
for (const xAxis in result) {
|
||||||
|
let yAxis1 = result[xAxis].filter(a => a.yAxis2 === -1).map(a => a.yAxis).reduce((a, b) => a + b, 0);
|
||||||
|
let yAxis2 = result[xAxis].filter(a => a.yAxis === -1).map(a => a.yAxis2).reduce((a, b) => a + b, 0);
|
||||||
|
allData.push({
|
||||||
|
groupName: 'users',
|
||||||
|
xAxis: xAxis,
|
||||||
|
yAxis: yAxis1,
|
||||||
|
yAxis2: -1,
|
||||||
|
yAxisIndex: 0,
|
||||||
|
}, {
|
||||||
|
groupName: 'transactions/s',
|
||||||
|
xAxis: xAxis,
|
||||||
|
yAxis: -1,
|
||||||
|
yAxis2: yAxis2,
|
||||||
|
yAxisIndex: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let yAxisList = allData.filter(m => m.yAxis2 === -1).map(m => m.yAxis);
|
||||||
|
let yAxis2List = allData.filter(m => m.yAxis === -1).map(m => m.yAxis2);
|
||||||
|
let yAxisListMax = this._getChartMax(yAxisList);
|
||||||
|
let yAxis2ListMax = this._getChartMax(yAxis2List);
|
||||||
|
loadOption.yAxis = [{
|
||||||
|
name: 'User',
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: yAxisListMax,
|
||||||
|
splitNumber: 5,
|
||||||
|
interval: yAxisListMax / 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Transactions/s',
|
||||||
|
type: 'value',
|
||||||
|
splitNumber: 5,
|
||||||
|
min: 0,
|
||||||
|
max: yAxis2ListMax,
|
||||||
|
interval: yAxis2ListMax / 5
|
||||||
|
}
|
||||||
|
];
|
||||||
|
this.loadOption = this.generateOption(loadOption, allData);
|
||||||
|
},
|
||||||
|
getResChart() {
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
let data = this.planReportTemplate.responseTimeChartData;
|
||||||
|
this.handleGetResChart(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleGetResChart(data) {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let resOption = {
|
||||||
|
color: color,
|
||||||
|
title: {
|
||||||
|
text: 'Response Time',
|
||||||
|
left: 'center',
|
||||||
|
top: 20,
|
||||||
|
textStyle: {
|
||||||
|
color: '#99743C'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: 'axis',
|
||||||
|
// extraCssText: 'z-index: 999;',
|
||||||
|
confine: true,
|
||||||
|
formatter: function (params, ticket, callback) {
|
||||||
|
let result = "";
|
||||||
|
let name = params[0].name;
|
||||||
|
result += name + "<br/>";
|
||||||
|
for (let i = 0; i < params.length; i++) {
|
||||||
|
let seriesName = params[i].seriesName;
|
||||||
|
if (seriesName.length > 100) {
|
||||||
|
seriesName = seriesName.substring(0, 100);
|
||||||
|
}
|
||||||
|
let value = params[i].value;
|
||||||
|
let marker = params[i].marker;
|
||||||
|
result += marker + seriesName + ": " + value[1] + "<br/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
xAxis: {},
|
||||||
|
series: []
|
||||||
|
};
|
||||||
|
|
||||||
|
let allData = [];
|
||||||
|
let result = groupBy(data, 'xAxis');
|
||||||
|
for (const xAxis in result) {
|
||||||
|
let yAxis1 = result[xAxis].filter(a => a.yAxis2 === -1).map(a => a.yAxis).reduce((a, b) => a + b, 0);
|
||||||
|
yAxis1 = yAxis1 / result[xAxis].length;
|
||||||
|
|
||||||
|
allData.push({
|
||||||
|
groupName: 'response',
|
||||||
|
xAxis: xAxis,
|
||||||
|
yAxis: -1,
|
||||||
|
yAxis2: yAxis1,
|
||||||
|
yAxisIndex: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let yAxisList = allData.filter(m => m.yAxis === -1).map(m => m.yAxis2);
|
||||||
|
let yAxisListMax = this._getChartMax(yAxisList);
|
||||||
|
resOption.yAxis = [
|
||||||
|
{
|
||||||
|
name: 'Response Time',
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: yAxisListMax,
|
||||||
|
interval: yAxisListMax / 5
|
||||||
|
}
|
||||||
|
];
|
||||||
|
this.resOption = this.generateOption(resOption, allData);
|
||||||
|
},
|
||||||
|
getErrorChart() {
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
let data = this.planReportTemplate.loadOverviewErrorChart;
|
||||||
|
this.handleGetErrorChart(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleGetErrorChart(data) {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let errorOption = {
|
||||||
|
color: color,
|
||||||
|
title: {
|
||||||
|
text: 'Errors',
|
||||||
|
left: 'center',
|
||||||
|
top: 20,
|
||||||
|
textStyle: {
|
||||||
|
color: '#99743C'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: 'axis',
|
||||||
|
// extraCssText: 'z-index: 999;',
|
||||||
|
confine: true,
|
||||||
|
formatter: function (params, ticket, callback) {
|
||||||
|
let result = "";
|
||||||
|
let name = params[0].name;
|
||||||
|
result += name + "<br/>";
|
||||||
|
for (let i = 0; i < params.length; i++) {
|
||||||
|
let seriesName = params[i].seriesName;
|
||||||
|
if (seriesName.length > 100) {
|
||||||
|
seriesName = seriesName.substring(0, 100);
|
||||||
|
}
|
||||||
|
let value = params[i].value;
|
||||||
|
let marker = params[i].marker;
|
||||||
|
result += marker + seriesName + ": " + value[1] + "<br/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
xAxis: {},
|
||||||
|
series: []
|
||||||
|
};
|
||||||
|
|
||||||
|
let allData = [];
|
||||||
|
let result = groupBy(data, 'xAxis');
|
||||||
|
for (const xAxis in result) {
|
||||||
|
let yAxis1 = result[xAxis].filter(a => a.yAxis2 === -1).map(a => a.yAxis).reduce((a, b) => a + b, 0);
|
||||||
|
|
||||||
|
allData.push({
|
||||||
|
groupName: 'errors',
|
||||||
|
xAxis: xAxis,
|
||||||
|
yAxis: -1,
|
||||||
|
yAxis2: yAxis1,
|
||||||
|
yAxisIndex: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let yAxisList = allData.filter(m => m.yAxis === -1).map(m => m.yAxis2);
|
||||||
|
let yAxisListMax = this._getChartMax(yAxisList);
|
||||||
|
errorOption.yAxis = [
|
||||||
|
{
|
||||||
|
name: 'No',
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: yAxisListMax,
|
||||||
|
interval: yAxisListMax / 5
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
this.errorOption = this.generateOption(errorOption, allData);
|
||||||
|
},
|
||||||
|
getResponseCodeChart() {
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
let data = this.planReportTemplate.responseCodeChartData;
|
||||||
|
this.handleGetResponseCodeChart(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleGetResponseCodeChart(data) {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let resCodeOption = {
|
||||||
|
color: color,
|
||||||
|
title: {
|
||||||
|
text: 'Response code',
|
||||||
|
left: 'center',
|
||||||
|
top: 20,
|
||||||
|
textStyle: {
|
||||||
|
color: '#99743C'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: 'axis',
|
||||||
|
// extraCssText: 'z-index: 999;',
|
||||||
|
confine: true,
|
||||||
|
formatter: function (params, ticket, callback) {
|
||||||
|
let result = "";
|
||||||
|
let name = params[0].name;
|
||||||
|
result += name + "<br/>";
|
||||||
|
for (let i = 0; i < params.length; i++) {
|
||||||
|
let seriesName = params[i].seriesName;
|
||||||
|
if (seriesName.length > 100) {
|
||||||
|
seriesName = seriesName.substring(0, 100);
|
||||||
|
}
|
||||||
|
let value = params[i].value;
|
||||||
|
let marker = params[i].marker;
|
||||||
|
result += marker + seriesName + ": " + value[1] + "<br/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
xAxis: {},
|
||||||
|
series: []
|
||||||
|
};
|
||||||
|
|
||||||
|
let allData = [];
|
||||||
|
let result = groupBy(data, 'xAxis');
|
||||||
|
for (const xAxis in result) {
|
||||||
|
let yAxis1 = result[xAxis].filter(a => a.yAxis2 === -1).map(a => a.yAxis).reduce((a, b) => a + b, 0);
|
||||||
|
|
||||||
|
allData.push({
|
||||||
|
groupName: 'codes',
|
||||||
|
xAxis: xAxis,
|
||||||
|
yAxis: -1,
|
||||||
|
yAxis2: yAxis1,
|
||||||
|
yAxisIndex: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let yAxisList = allData.filter(m => m.yAxis === -1).map(m => m.yAxis2);
|
||||||
|
let yAxisListMax = this._getChartMax(yAxisList);
|
||||||
|
resCodeOption.yAxis = [
|
||||||
|
{
|
||||||
|
name: 'No',
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: yAxisListMax,
|
||||||
|
interval: yAxisListMax / 5
|
||||||
|
}
|
||||||
|
];
|
||||||
|
this.resCodeOption = this.generateOption(resCodeOption, allData);
|
||||||
|
},
|
||||||
|
generateOption(option, data) {
|
||||||
|
let chartData = data;
|
||||||
|
let legend = [], series = {}, xAxis = [], seriesData = [], yAxisIndex = {};
|
||||||
|
chartData.forEach(item => {
|
||||||
|
if (!xAxis.includes(item.xAxis)) {
|
||||||
|
xAxis.push(item.xAxis);
|
||||||
|
}
|
||||||
|
xAxis.sort();
|
||||||
|
let name = item.groupName;
|
||||||
|
yAxisIndex[name] = item.yAxisIndex;
|
||||||
|
if (!legend.includes(name)) {
|
||||||
|
legend.push(name);
|
||||||
|
series[name] = [];
|
||||||
|
}
|
||||||
|
if (item.yAxis === -1) {
|
||||||
|
series[name].splice(xAxis.indexOf(item.xAxis), 0, [item.xAxis, item.yAxis2.toFixed(2)]);
|
||||||
|
} else {
|
||||||
|
series[name].splice(xAxis.indexOf(item.xAxis), 0, [item.xAxis, item.yAxis.toFixed(2)]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.$set(option.legend, "data", legend);
|
||||||
|
this.$set(option.legend, "type", "scroll");
|
||||||
|
this.$set(option.legend, "bottom", "10px");
|
||||||
|
this.$set(option.xAxis, "data", xAxis);
|
||||||
|
for (let name in series) {
|
||||||
|
let d = series[name];
|
||||||
|
d.sort((a, b) => a[0].localeCompare(b[0]));
|
||||||
|
let items = {
|
||||||
|
name: name,
|
||||||
|
type: 'line',
|
||||||
|
data: d,
|
||||||
|
smooth: true,
|
||||||
|
sampling: 'lttb',
|
||||||
|
showSymbol: false,
|
||||||
|
animation: !this.export,
|
||||||
|
yAxisIndex: yAxisIndex[name]
|
||||||
|
};
|
||||||
|
seriesData.push(items);
|
||||||
|
}
|
||||||
|
this.$set(option, "series", seriesData);
|
||||||
|
return option;
|
||||||
|
},
|
||||||
|
_getChartMax(arr) {
|
||||||
|
const max = Math.max(...arr);
|
||||||
|
return Math.ceil(max / 4.5) * 5;
|
||||||
|
},
|
||||||
|
_unique(arr) {
|
||||||
|
return Array.from(new Set(arr));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
report: {
|
||||||
|
handler(val) {
|
||||||
|
if (!val.status || !val.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let status = val.status;
|
||||||
|
this.id = val.id;
|
||||||
|
if (status === "Completed" || status === "Running") {
|
||||||
|
this.initTableData();
|
||||||
|
} else {
|
||||||
|
this.maxUsers = '0';
|
||||||
|
this.avgThroughput = '0';
|
||||||
|
this.avgTransactions = '0';
|
||||||
|
this.errors = '0';
|
||||||
|
this.avgResponseTime = '0';
|
||||||
|
this.responseTime90 = '0';
|
||||||
|
this.avgBandwidth = '0';
|
||||||
|
this.loadOption = {};
|
||||||
|
this.resOption = {};
|
||||||
|
this.errorOption = {};
|
||||||
|
this.resCodeOption = {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
planReportTemplate: {
|
||||||
|
handler() {
|
||||||
|
if (this.planReportTemplate) {
|
||||||
|
this.initTableData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.ms-card-data {
|
||||||
|
text-align: left;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-card-desc {
|
||||||
|
display: block;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-card-data-digital {
|
||||||
|
font-size: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-card-data-unit {
|
||||||
|
color: #8492a6;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-card-index-1 .ms-card-data-digital {
|
||||||
|
color: #44b349;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-card-index-1 {
|
||||||
|
border-left-color: #44b349;
|
||||||
|
border-left-width: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-card-index-2 .ms-card-data-digital {
|
||||||
|
color: #65A2FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-card-index-2 {
|
||||||
|
border-left-color: #65A2FF;
|
||||||
|
border-left-width: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-card-index-3 .ms-card-data-digital {
|
||||||
|
color: #E6113C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-card-index-3 {
|
||||||
|
border-left-color: #E6113C;
|
||||||
|
border-left-width: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-card-index-4 .ms-card-data-digital {
|
||||||
|
color: #99743C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-card-index-4 {
|
||||||
|
border-left-color: #99743C;
|
||||||
|
border-left-width: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-card-index-5 .ms-card-data-digital {
|
||||||
|
color: #99743C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-card-index-5 {
|
||||||
|
border-left-color: #99743C;
|
||||||
|
border-left-width: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-card-index-6 .ms-card-data-digital {
|
||||||
|
color: #3C9899;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-card-index-6 {
|
||||||
|
border-left-color: #3C9899;
|
||||||
|
border-left-width: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-config {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,22 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<span v-if="!reportId">{{$t('commons.not_performed_yet')}}</span>
|
|
||||||
<ms-api-report-view-detail v-if="reportId" :report-id="reportId"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
// import MsApiReportViewDetail from "@/business/api/relation/ApiReportViewDetail";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "ApiTestResult",
|
|
||||||
components: {MsApiReportViewDetail},
|
|
||||||
props: ['reportId'],
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
|
@ -1,135 +0,0 @@
|
||||||
<template>
|
|
||||||
<ms-container>
|
|
||||||
<ms-main-container>
|
|
||||||
<el-card>
|
|
||||||
<!--<el-card v-loading="result.loading">-->
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="10">
|
|
||||||
<el-input :disabled="true" :placeholder="$t('load_test.input_name')" v-model="test.name" class="input-with-select">
|
|
||||||
<template v-slot:prepend>
|
|
||||||
<el-select :disabled="true" v-model="project.name" :placeholder="$t('load_test.select_project')" slot="prepend">
|
|
||||||
</el-select>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12" :offset="2">
|
|
||||||
<el-button :disabled="isReadOnly" type="primary" plain @click="runTest">执行</el-button>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-tabs class="test-config" v-model="active">
|
|
||||||
<el-tab-pane :label="$t('load_test.basic_config')">
|
|
||||||
<performance-basic-config :is-read-only="true" :test="test" ref="basicConfig"/>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane :label="$t('load_test.pressure_config')">
|
|
||||||
<performance-pressure-config :is-read-only="true" :test="test" :test-id="id" ref="pressureConfig"/>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane :label="$t('load_test.advanced_config')" class="advanced-config">
|
|
||||||
<performance-advanced-config :read-only="true" :test-id="id" ref="advancedConfig"/>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
</el-card>
|
|
||||||
</ms-main-container>
|
|
||||||
</ms-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import MsContainer from "metersphere-frontend/src/components/MsContainer";
|
|
||||||
import MsMainContainer from "metersphere-frontend/src/components/MsMainContainer";
|
|
||||||
// import PerformanceBasicConfig from "../../../../../performance/test/components/PerformanceBasicConfig";
|
|
||||||
// import PerformancePressureConfig from "../../../../../performance/test/components/PerformancePressureConfig";
|
|
||||||
// import PerformanceAdvancedConfig from "../../../../../performance/test/components/PerformanceAdvancedConfig";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "PerformanceTestDetail",
|
|
||||||
components: {
|
|
||||||
PerformanceAdvancedConfig,
|
|
||||||
PerformancePressureConfig,
|
|
||||||
PerformanceBasicConfig,
|
|
||||||
MsMainContainer,
|
|
||||||
MsContainer
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
result: {},
|
|
||||||
test: {},
|
|
||||||
savePath: "/performance/save",
|
|
||||||
editPath: "/performance/edit",
|
|
||||||
runPath: "/performance/run",
|
|
||||||
project: {},
|
|
||||||
projectId: '',
|
|
||||||
active: '0',
|
|
||||||
tabs: [{
|
|
||||||
title: this.$t('load_test.basic_config'),
|
|
||||||
id: '0',
|
|
||||||
component: 'PerformanceBasicConfig'
|
|
||||||
}, {
|
|
||||||
title: this.$t('load_test.pressure_config'),
|
|
||||||
id: '1',
|
|
||||||
component: 'PerformancePressureConfig'
|
|
||||||
}, {
|
|
||||||
title: this.$t('load_test.advanced_config'),
|
|
||||||
id: '2',
|
|
||||||
component: 'PerformanceAdvancedConfig'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
id: String,
|
|
||||||
isReadOnly: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
init() {
|
|
||||||
this.getTest();
|
|
||||||
},
|
|
||||||
getProject(projectId) {
|
|
||||||
this.$get("/project/get/" + projectId, response => {
|
|
||||||
this.project = response.data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getTest() {
|
|
||||||
if (this.id) {
|
|
||||||
this.result = this.$get('/performance/get/' + this.id, response => {
|
|
||||||
if (response.data) {
|
|
||||||
this.test = response.data;
|
|
||||||
this.getProject(this.test.projectId);
|
|
||||||
} else {
|
|
||||||
this.test = {};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
runTest() {
|
|
||||||
this.result = this.$post(this.runPath, {id: this.test.id, triggerMode: 'MANUAL'}, (response) => {
|
|
||||||
this.$success(this.$t('load_test.is_running'));
|
|
||||||
this.$emit('runTest', response.data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
.test-config {
|
|
||||||
margin-top: 15px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-select {
|
|
||||||
min-width: 130px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-test-container .input-with-select .el-input-group__prepend {
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-config {
|
|
||||||
height: calc(100vh - 280px);
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,241 +0,0 @@
|
||||||
<template>
|
|
||||||
<ms-container>
|
|
||||||
<ms-main-container>
|
|
||||||
<span v-if="!reportId">{{$t('commons.not_performed_yet')}}</span>
|
|
||||||
<el-card v-loading="result.loading" v-if="reportId">
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="16">
|
|
||||||
<el-row>
|
|
||||||
<el-breadcrumb separator-class="el-icon-arrow-right">
|
|
||||||
<el-breadcrumb-item :to="{ path: '/performance/test/' + this.projectId }">{{projectName}}
|
|
||||||
</el-breadcrumb-item>
|
|
||||||
<el-breadcrumb-item :to="{ path: '/performance/test/edit/' + this.testId }">{{testName}}
|
|
||||||
</el-breadcrumb-item>
|
|
||||||
<el-breadcrumb-item>{{reportName}}</el-breadcrumb-item>
|
|
||||||
</el-breadcrumb>
|
|
||||||
</el-row>
|
|
||||||
<!--<el-row class="ms-report-view-btns">-->
|
|
||||||
<!--<el-button :disabled="isReadOnly" type="primary" plain size="mini">{{$t('report.test_stop_now')}}</el-button>-->
|
|
||||||
<!--<el-button :disabled="isReadOnly" type="success" plain size="mini">{{$t('report.test_execute_again')}}</el-button>-->
|
|
||||||
<!--<el-button :disabled="isReadOnly" type="info" plain size="mini">{{$t('report.export')}}</el-button>-->
|
|
||||||
<!--<el-button :disabled="isReadOnly" type="warning" plain size="mini">{{$t('report.compare')}}</el-button>-->
|
|
||||||
<!--</el-row>-->
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<span class="ms-report-time-desc">
|
|
||||||
{{$t('report.test_duration', [this.minutes, this.seconds])}}
|
|
||||||
</span>
|
|
||||||
<span class="ms-report-time-desc">
|
|
||||||
{{$t('report.test_start_time')}}:{{startTime}}
|
|
||||||
</span>
|
|
||||||
<span class="ms-report-time-desc">
|
|
||||||
{{$t('report.test_end_time')}}:{{endTime}}
|
|
||||||
</span>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-divider></el-divider>
|
|
||||||
|
|
||||||
<el-tabs v-model="active">
|
|
||||||
<el-tab-pane :label="$t('report.test_overview')">
|
|
||||||
<test-overview :report="report"/>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane :label="$t('report.test_details')">
|
|
||||||
<ms-report-test-details :report="report" ref="testDetails"/>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane :label="$t('report.test_request_statistics')">
|
|
||||||
<request-statistics :report="report"/>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane :label="$t('report.test_error_log')">
|
|
||||||
<error-log :report="report"/>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane :label="$t('report.test_log_details')">
|
|
||||||
<log-details :report="report"/>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
|
|
||||||
</el-card>
|
|
||||||
</ms-main-container>
|
|
||||||
</ms-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import MsMainContainer from "metersphere-frontend/src/components/MsMainContainer";
|
|
||||||
import MsContainer from "metersphere-frontend/src/components/MsContainer";
|
|
||||||
// import LogDetails from "metersphere-frontend/src/components/report/LogDetails";
|
|
||||||
// import ErrorLog from "metersphere-frontend/src/components/report/ErrorLog";
|
|
||||||
// import RequestStatistics from "metersphere-frontend/src/components/report/RequestStatistics";
|
|
||||||
// import TestOverview from "metersphere-frontend/src/components/report/TestOverview";
|
|
||||||
// import MsReportTestDetails from "metersphere-frontend/src/components/report/TestDetails";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "PerformanceTestResult",
|
|
||||||
components: {
|
|
||||||
TestOverview,
|
|
||||||
MsReportTestDetails,
|
|
||||||
RequestStatistics,
|
|
||||||
ErrorLog,
|
|
||||||
LogDetails,
|
|
||||||
MsContainer,
|
|
||||||
MsMainContainer
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
result: {},
|
|
||||||
active: '0',
|
|
||||||
status: '',
|
|
||||||
reportName: '',
|
|
||||||
testId: '',
|
|
||||||
testName: '',
|
|
||||||
projectId: '',
|
|
||||||
projectName: '',
|
|
||||||
startTime: '0',
|
|
||||||
endTime: '0',
|
|
||||||
minutes: '0',
|
|
||||||
seconds: '0',
|
|
||||||
title: 'Logging',
|
|
||||||
report: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
reportId: String,
|
|
||||||
isReadOnly: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.init();
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
reportId() {
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
initBreadcrumb() {
|
|
||||||
if (this.reportId) {
|
|
||||||
this.result = this.$get("/performance/report/test/pro/info/" + this.reportId, res => {
|
|
||||||
let data = res.data;
|
|
||||||
if (data) {
|
|
||||||
this.reportName = data.name;
|
|
||||||
this.testId = data.testId;
|
|
||||||
this.testName = data.testName;
|
|
||||||
this.projectId = data.projectId;
|
|
||||||
this.projectName = data.projectName;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
initReportTimeInfo() {
|
|
||||||
if (this.reportId) {
|
|
||||||
this.result = this.$get("/performance/report/content/report_time/" + this.reportId)
|
|
||||||
.then(res => {
|
|
||||||
let data = res.data.data;
|
|
||||||
if (data) {
|
|
||||||
this.startTime = data.startTime;
|
|
||||||
this.endTime = data.endTime;
|
|
||||||
let duration = data.duration;
|
|
||||||
this.minutes = Math.floor(duration / 60);
|
|
||||||
this.seconds = duration % 60;
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
this.clearData();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
checkReportStatus() {
|
|
||||||
if (!this.report) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (this.report.status) {
|
|
||||||
case 'Error':
|
|
||||||
// this.$warning(this.$t('report.generation_error'));
|
|
||||||
break;
|
|
||||||
case 'Starting':
|
|
||||||
this.$warning(this.$t('report.start_status'));
|
|
||||||
break;
|
|
||||||
case 'Reporting':
|
|
||||||
case 'Running':
|
|
||||||
case 'Completed':
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clearData() {
|
|
||||||
this.startTime = '0';
|
|
||||||
this.endTime = '0';
|
|
||||||
this.minutes = '0';
|
|
||||||
this.seconds = '0';
|
|
||||||
},
|
|
||||||
init() {
|
|
||||||
this.getReport();
|
|
||||||
this.getReportView();
|
|
||||||
},
|
|
||||||
getReportView() {
|
|
||||||
if (this.reportId) {
|
|
||||||
this.$get("/performance/report/test/pro/info/" + this.reportId, response => {
|
|
||||||
let data = response.data;
|
|
||||||
if (data) {
|
|
||||||
this.status = data.status;
|
|
||||||
this.reportName = data.name;
|
|
||||||
this.testName = data.testName;
|
|
||||||
this.projectName = data.projectName;
|
|
||||||
|
|
||||||
this.$set(this.report, "id", this.reportId);
|
|
||||||
this.$set(this.report, "status", data.status);
|
|
||||||
|
|
||||||
if (this.status === "Completed") {
|
|
||||||
this.result = this.$get("/performance/report/content/report_time/" + this.reportId).then(res => {
|
|
||||||
let data = res.data.data;
|
|
||||||
if (data) {
|
|
||||||
this.startTime = data.startTime;
|
|
||||||
this.endTime = data.endTime;
|
|
||||||
let duration = data.duration;
|
|
||||||
this.minutes = Math.floor(duration / 60);
|
|
||||||
this.seconds = duration % 60;
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
this.clearData();
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.clearData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getReport() {
|
|
||||||
if (this.reportId) {
|
|
||||||
this.result = this.$get("/performance/report/" + this.reportId, res => {
|
|
||||||
let data = res.data;
|
|
||||||
if (data) {
|
|
||||||
this.status = data.status;
|
|
||||||
this.$set(this.report, "id", this.reportId);
|
|
||||||
this.$set(this.report, "status", data.status);
|
|
||||||
if (this.status === "Completed") {
|
|
||||||
this.initReportTimeInfo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.initBreadcrumb();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
.ms-report-view-btns {
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ms-report-time-desc {
|
|
||||||
text-align: left;
|
|
||||||
display: block;
|
|
||||||
color: #5C7878;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
Loading…
Reference in New Issue