This commit is contained in:
chenjianxing 2020-08-10 21:59:26 +08:00
commit 22950f0893
27 changed files with 857 additions and 300 deletions

View File

@ -158,6 +158,12 @@
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_functions</artifactId>
<version>${jmeter.version}</version>
</dependency>
<!-- Zookeeper -->
<dependency>
<groupId>org.apache.dubbo</groupId>

View File

@ -1,8 +1,9 @@
package io.metersphere.base.domain;
import java.io.Serializable;
import lombok.Data;
import java.io.Serializable;
@Data
public class LoadTestReport implements Serializable {
private String id;
@ -21,7 +22,5 @@ public class LoadTestReport implements Serializable {
private String triggerMode;
private String description;
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,18 @@
package io.metersphere.base.domain;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class LoadTestReportWithBLOBs extends LoadTestReport implements Serializable {
private String description;
private String loadConfiguration;
private static final long serialVersionUID = 1L;
}

View File

@ -2,9 +2,11 @@ package io.metersphere.base.mapper;
import io.metersphere.base.domain.LoadTestReport;
import io.metersphere.base.domain.LoadTestReportExample;
import java.util.List;
import io.metersphere.base.domain.LoadTestReportWithBLOBs;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface LoadTestReportMapper {
long countByExample(LoadTestReportExample example);
@ -12,25 +14,25 @@ public interface LoadTestReportMapper {
int deleteByPrimaryKey(String id);
int insert(LoadTestReport record);
int insert(LoadTestReportWithBLOBs record);
int insertSelective(LoadTestReport record);
int insertSelective(LoadTestReportWithBLOBs record);
List<LoadTestReport> selectByExampleWithBLOBs(LoadTestReportExample example);
List<LoadTestReportWithBLOBs> selectByExampleWithBLOBs(LoadTestReportExample example);
List<LoadTestReport> selectByExample(LoadTestReportExample example);
LoadTestReport selectByPrimaryKey(String id);
LoadTestReportWithBLOBs selectByPrimaryKey(String id);
int updateByExampleSelective(@Param("record") LoadTestReport record, @Param("example") LoadTestReportExample example);
int updateByExampleSelective(@Param("record") LoadTestReportWithBLOBs record, @Param("example") LoadTestReportExample example);
int updateByExampleWithBLOBs(@Param("record") LoadTestReport record, @Param("example") LoadTestReportExample example);
int updateByExampleWithBLOBs(@Param("record") LoadTestReportWithBLOBs record, @Param("example") LoadTestReportExample example);
int updateByExample(@Param("record") LoadTestReport record, @Param("example") LoadTestReportExample example);
int updateByPrimaryKeySelective(LoadTestReport record);
int updateByPrimaryKeySelective(LoadTestReportWithBLOBs record);
int updateByPrimaryKeyWithBLOBs(LoadTestReport record);
int updateByPrimaryKeyWithBLOBs(LoadTestReportWithBLOBs record);
int updateByPrimaryKey(LoadTestReport record);
}

View File

@ -11,8 +11,9 @@
<result column="user_id" jdbcType="VARCHAR" property="userId" />
<result column="trigger_mode" jdbcType="VARCHAR" property="triggerMode" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.LoadTestReport">
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.LoadTestReportWithBLOBs">
<result column="description" jdbcType="LONGVARCHAR" property="description" />
<result column="load_configuration" jdbcType="LONGVARCHAR" property="loadConfiguration" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -76,7 +77,7 @@
id, test_id, `name`, create_time, update_time, `status`, user_id, trigger_mode
</sql>
<sql id="Blob_Column_List">
description
description, load_configuration
</sql>
<select id="selectByExampleWithBLOBs" parameterType="io.metersphere.base.domain.LoadTestReportExample" resultMap="ResultMapWithBLOBs">
select
@ -126,17 +127,17 @@
<include refid="Example_Where_Clause" />
</if>
</delete>
<insert id="insert" parameterType="io.metersphere.base.domain.LoadTestReport">
insert into load_test_report (id, test_id, `name`,
<insert id="insert" parameterType="io.metersphere.base.domain.LoadTestReportWithBLOBs">
INSERT INTO load_test_report (id, test_id, `name`,
create_time, update_time, `status`,
user_id, trigger_mode, description
)
values (#{id,jdbcType=VARCHAR}, #{testId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
user_id, trigger_mode, description,
load_configuration)
VALUES (#{id,jdbcType=VARCHAR}, #{testId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
#{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{status,jdbcType=VARCHAR},
#{userId,jdbcType=VARCHAR}, #{triggerMode,jdbcType=VARCHAR}, #{description,jdbcType=LONGVARCHAR}
)
#{userId,jdbcType=VARCHAR}, #{triggerMode,jdbcType=VARCHAR}, #{description,jdbcType=LONGVARCHAR},
#{loadConfiguration,jdbcType=LONGVARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.LoadTestReport">
<insert id="insertSelective" parameterType="io.metersphere.base.domain.LoadTestReportWithBLOBs">
insert into load_test_report
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -166,6 +167,9 @@
<if test="description != null">
description,
</if>
<if test="loadConfiguration != null">
load_configuration,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -195,6 +199,9 @@
<if test="description != null">
#{description,jdbcType=LONGVARCHAR},
</if>
<if test="loadConfiguration != null">
#{loadConfiguration,jdbcType=LONGVARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.LoadTestReportExample" resultType="java.lang.Long">
@ -233,6 +240,9 @@
<if test="record.description != null">
description = #{record.description,jdbcType=LONGVARCHAR},
</if>
<if test="record.loadConfiguration != null">
load_configuration = #{record.loadConfiguration,jdbcType=LONGVARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -248,7 +258,8 @@
`status` = #{record.status,jdbcType=VARCHAR},
user_id = #{record.userId,jdbcType=VARCHAR},
trigger_mode = #{record.triggerMode,jdbcType=VARCHAR},
description = #{record.description,jdbcType=LONGVARCHAR}
description = #{record.description,jdbcType=LONGVARCHAR},
load_configuration = #{record.loadConfiguration,jdbcType=LONGVARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -267,7 +278,7 @@
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.base.domain.LoadTestReport">
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.base.domain.LoadTestReportWithBLOBs">
update load_test_report
<set>
<if test="testId != null">
@ -294,10 +305,13 @@
<if test="description != null">
description = #{description,jdbcType=LONGVARCHAR},
</if>
<if test="loadConfiguration != null">
load_configuration = #{loadConfiguration,jdbcType=LONGVARCHAR},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.base.domain.LoadTestReport">
<update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.base.domain.LoadTestReportWithBLOBs">
update load_test_report
set test_id = #{testId,jdbcType=VARCHAR},
`name` = #{name,jdbcType=VARCHAR},
@ -306,7 +320,8 @@
`status` = #{status,jdbcType=VARCHAR},
user_id = #{userId,jdbcType=VARCHAR},
trigger_mode = #{triggerMode,jdbcType=VARCHAR},
description = #{description,jdbcType=LONGVARCHAR}
description = #{description,jdbcType=LONGVARCHAR},
load_configuration = #{loadConfiguration,jdbcType=LONGVARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKey" parameterType="io.metersphere.base.domain.LoadTestReport">

View File

@ -1,6 +1,5 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.base.domain.LoadTestReport;
import io.metersphere.dto.DashboardTestDTO;
import io.metersphere.dto.ReportDTO;
import io.metersphere.performance.controller.request.ReportRequest;
@ -14,8 +13,6 @@ public interface ExtLoadTestReportMapper {
ReportDTO getReportTestAndProInfo(@Param("id") String id);
LoadTestReport selectByPrimaryKey(String id);
List<DashboardTestDTO> selectDashboardTests(@Param("workspaceId") String workspaceId, @Param("startTimestamp") long startTimestamp);
List<String> selectResourceId(@Param("reportId") String reportId);

View File

@ -125,13 +125,6 @@
where ltr.id = #{id}
</select>
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM load_test_report
WHERE id = #{id,jdbcType=VARCHAR}
</select>
<select id="selectDashboardTests" resultType="io.metersphere.dto.DashboardTestDTO">
SELECT create_time AS date, count(load_test_report.id) AS count,
date_format(from_unixtime(create_time / 1000), '%Y-%m-%d') AS x

View File

@ -154,7 +154,7 @@
</include>
</if>
<if test="request.name != null">
and test_case.name like CONCAT('%', #{request.name},'%')
and (test_case.name like CONCAT('%', #{request.name},'%') or test_case.num like CONCAT('%', #{request.name},'%'))
</if>
<if test="request.nodeIds != null and request.nodeIds.size() > 0">
and test_case.node_id in

View File

@ -126,7 +126,7 @@
</include>
</if>
<if test="request.name != null">
and test_case.name like CONCAT('%', #{request.name},'%')
and (test_case.name like CONCAT('%', #{request.name},'%') or test_case.num like CONCAT('%', #{request.name},'%'))
</if>
<if test="request.id != null">
and test_case.id = #{request.id}
@ -185,7 +185,14 @@
<if test="request.orders != null and request.orders.size() > 0">
order by
<foreach collection="request.orders" separator="," item="order">
test_plan_test_case.${order.name} ${order.type}
<choose>
<when test="order.name == 'num'">
test_case.num ${order.type}
</when>
<otherwise>
test_plan_test_case.${order.name} ${order.type}
</otherwise>
</choose>
</foreach>
</if>
</select>

View File

@ -2,8 +2,8 @@ package io.metersphere.performance.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.LoadTestReport;
import io.metersphere.base.domain.LoadTestReportLog;
import io.metersphere.base.domain.LoadTestReportWithBLOBs;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
@ -94,7 +94,7 @@ public class PerformanceReportController {
}
@GetMapping("/{reportId}")
public LoadTestReport getLoadTestReport(@PathVariable String reportId) {
public LoadTestReportWithBLOBs getLoadTestReport(@PathVariable String reportId) {
return reportService.getLoadTestReport(reportId);
}

View File

@ -226,8 +226,6 @@ public class PerformanceTestService {
startEngine(loadTest, engine, request.getTriggerMode());
// todo通过调用stop方法能够停止正在运行的engine但是如果部署了多个backend实例页面发送的停止请求如何定位到具体的engine
return engine.getReportId();
}
@ -257,7 +255,7 @@ public class PerformanceTestService {
}
private void startEngine(LoadTestWithBLOBs loadTest, Engine engine, String triggerMode) {
LoadTestReport testReport = new LoadTestReport();
LoadTestReportWithBLOBs testReport = new LoadTestReportWithBLOBs();
testReport.setId(engine.getReportId());
testReport.setCreateTime(engine.getStartTime());
testReport.setUpdateTime(engine.getStartTime());
@ -277,6 +275,7 @@ public class PerformanceTestService {
loadTest.setStatus(PerformanceTestStatus.Starting.name());
loadTestMapper.updateByPrimaryKeySelective(loadTest);
// 启动正常插入 report
testReport.setLoadConfiguration(loadTest.getLoadConfiguration());
testReport.setStatus(PerformanceTestStatus.Starting.name());
loadTestReportMapper.insertSelective(testReport);

View File

@ -169,8 +169,8 @@ public class ReportService {
}
}
public LoadTestReport getLoadTestReport(String id) {
return extLoadTestReportMapper.selectByPrimaryKey(id);
public LoadTestReportWithBLOBs getLoadTestReport(String id) {
return loadTestReportMapper.selectByPrimaryKey(id);
}
public List<LogDetailDTO> getReportLogResource(String reportId) {
@ -241,7 +241,7 @@ public class ReportService {
}
public void updateStatus(String reportId, String status) {
LoadTestReport report = new LoadTestReport();
LoadTestReportWithBLOBs report = new LoadTestReportWithBLOBs();
report.setId(reportId);
report.setStatus(status);
loadTestReportMapper.updateByPrimaryKeySelective(report);

View File

@ -0,0 +1,2 @@
ALTER TABLE load_test_report
ADD load_configuration LONGTEXT NULL;

View File

@ -76,6 +76,7 @@
</span>
<p>{{ $t('api_test.request.parameters_filter_example') }}@string(10) | md5 | substr: 1, 3</p>
<p>{{ $t('api_test.request.parameters_filter_example') }}@integer(1, 5) | concat:_metersphere</p>
<p><strong>{{ $t('api_test.request.parameters_filter_tips') }}</strong></p>
</div>
</div>
</el-dialog>
@ -84,7 +85,7 @@
<script>
import {KeyValue} from "../model/ScenarioModel";
import {MOCKJS_FUNC} from "@/common/js/constants";
import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants";
import {calculate} from "@/business/components/api/test/model/ScenarioModel";
export default {
@ -159,7 +160,7 @@ export default {
};
},
funcSearch(queryString, cb) {
let funcs = MOCKJS_FUNC;
let funcs = MOCKJS_FUNC.concat(JMETER_FUNC);
let results = queryString ? funcs.filter(this.funcFilter(queryString)) : funcs;
// callback
cb(results);

View File

@ -275,29 +275,23 @@ export class DubboSample extends DefaultTestElement {
}
export class HTTPSamplerProxy extends DefaultTestElement {
constructor(testName, request) {
constructor(testName, options = {}) {
super('HTTPSamplerProxy', 'HttpTestSampleGui', 'HTTPSamplerProxy', testName);
this.request = request || {};
if (request.useEnvironment) {
this.stringProp("HTTPSampler.domain", request.domain);
this.stringProp("HTTPSampler.protocol", request.protocol);
this.stringProp("HTTPSampler.path", this.request.path);
} else {
this.stringProp("HTTPSampler.domain", this.request.hostname);
this.stringProp("HTTPSampler.protocol", this.request.protocol.split(":")[0]);
this.stringProp("HTTPSampler.path", this.request.pathname);
}
this.stringProp("HTTPSampler.method", this.request.method);
this.stringProp("HTTPSampler.contentEncoding", this.request.encoding, "UTF-8");
if (!this.request.port) {
this.stringProp("HTTPSampler.domain", options.domain);
this.stringProp("HTTPSampler.protocol", options.protocol);
this.stringProp("HTTPSampler.path", options.path);
this.stringProp("HTTPSampler.method", options.method);
this.stringProp("HTTPSampler.contentEncoding", options.encoding, "UTF-8");
if (!options.port) {
this.stringProp("HTTPSampler.port", "");
} else {
this.stringProp("HTTPSampler.port", this.request.port);
this.stringProp("HTTPSampler.port", options.port);
}
this.boolProp("HTTPSampler.follow_redirects", this.request.follow, true);
this.boolProp("HTTPSampler.use_keepalive", this.request.keepalive, true);
this.boolProp("HTTPSampler.follow_redirects", options.follow, true);
this.boolProp("HTTPSampler.use_keepalive", options.keepalive, true);
}
}

View File

@ -43,6 +43,10 @@ export const calculate = function (itemValue) {
return;
}
try {
if (itemValue.trim().startsWith("${")) {
// jmeter 内置函数不做处理
return itemValue;
}
let funcs = itemValue.split("|");
let value = Mock.mock(funcs[0].trim());
if (funcs.length === 1) {
@ -693,14 +697,14 @@ class JMXHttpRequest {
request.url = 'http://' + request.url;
}
let url = new URL(request.url);
this.hostname = decodeURIComponent(url.hostname);
this.domain = decodeURIComponent(url.hostname);
this.port = url.port;
this.protocol = url.protocol.split(":")[0];
this.pathname = this.getPostQueryParameters(request, decodeURIComponent(url.pathname));
this.path = this.getPostQueryParameters(request, decodeURIComponent(url.pathname));
} else {
this.domain = environment.domain;
this.port = environment.port;
this.protocol = environment.protocol;
this.domain = environment.domain;
let url = new URL(environment.protocol + "://" + environment.socket);
this.path = this.getPostQueryParameters(request, decodeURIComponent(url.pathname + (request.path ? request.path : '')));
}
@ -719,7 +723,7 @@ class JMXHttpRequest {
for (let i = 0; i < parameters.length; i++) {
let parameter = parameters[i];
path += (parameter.name + '=' + parameter.value);
if (i != parameters.length - 1) {
if (i !== parameters.length - 1) {
path += '&';
}
}

View File

@ -33,6 +33,7 @@ import TestTrack from "../../track/TestTrack";
import ApiReportList from "../../api/report/ApiReportList";
import axios from "axios";
import ApiKeys from "../../settings/personal/ApiKeys";
import ServiceIntegration from "../../settings/organization/ServiceIntegration";
const requireContext = require.context('@/business/components/xpack/', true, /router\.js$/)
@ -70,6 +71,10 @@ const router = new VueRouter({
path: 'organizationworkspace',
component: OrganizationWorkspace,
},
{
path: 'serviceintegration',
component: ServiceIntegration,
},
{
path: 'personsetting',
component: PersonSetting

View File

@ -6,21 +6,21 @@
<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 :to="{ path: '/performance/test/' + this.projectId }">{{ projectName }}
</el-breadcrumb-item>
<el-breadcrumb-item :to="{ path: '/performance/test/edit/' + this.testId }">{{testName}}
<el-breadcrumb-item :to="{ path: '/performance/test/edit/' + this.testId }">{{ testName }}
</el-breadcrumb-item>
<el-breadcrumb-item>{{reportName}}</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 || report.status !== 'Running'" type="primary" plain size="mini"
@click="dialogFormVisible=true">
{{$t('report.test_stop_now')}}
{{ $t('report.test_stop_now') }}
</el-button>
<el-button :disabled="isReadOnly || report.status !== 'Completed'" type="success" plain size="mini"
@click="rerun(testId)">
{{$t('report.test_execute_again')}}
{{ $t('report.test_execute_again') }}
</el-button>
<!--<el-button :disabled="isReadOnly" type="info" plain size="mini">
{{$t('report.export')}}
@ -32,13 +32,13 @@
</el-col>
<el-col :span="8">
<span class="ms-report-time-desc">
{{$t('report.test_duration', [this.minutes, this.seconds])}}
{{ $t('report.test_duration', [this.minutes, this.seconds]) }}
</span>
<span class="ms-report-time-desc">
{{$t('report.test_start_time')}}{{startTime}}
{{ $t('report.test_start_time') }}{{ startTime }}
</span>
<span class="ms-report-time-desc">
{{$t('report.test_end_time')}}{{endTime}}
{{ $t('report.test_end_time') }}{{ endTime }}
</span>
</el-col>
</el-row>
@ -46,8 +46,10 @@
<el-divider/>
<el-tabs v-model="active" type="border-card" :stretch="true">
<el-tab-pane :label="$t('load_test.pressure_config')">
<ms-performance-pressure-config :is-read-only="true" :report="report"/>
</el-tab-pane>
<el-tab-pane :label="$t('report.test_overview')">
<!-- <ms-report-test-overview :id="reportId" :status="status"/>-->
<ms-report-test-overview :report="report" ref="testOverview"/>
</el-tab-pane>
<el-tab-pane :label="$t('report.test_request_statistics')">
@ -66,9 +68,9 @@
<p v-html="$t('report.force_stop_tips')"/>
<p v-html="$t('report.stop_tips')"/>
<div slot="footer" class="dialog-footer">
<el-button type="danger" size="small" @click="stopTest(true)">{{$t('report.force_stop_btn')}}
<el-button type="danger" size="small" @click="stopTest(true)">{{ $t('report.force_stop_btn') }}
</el-button>
<el-button type="primary" size="small" @click="stopTest(false)">{{$t('report.stop_btn')}}
<el-button type="primary" size="small" @click="stopTest(false)">{{ $t('report.stop_btn') }}
</el-button>
</div>
</el-dialog>
@ -77,244 +79,250 @@
</template>
<script>
import MsReportErrorLog from './components/ErrorLog';
import MsReportLogDetails from './components/LogDetails';
import MsReportRequestStatistics from './components/RequestStatistics';
import MsReportTestOverview from './components/TestOverview';
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import {checkoutTestManagerOrTestUser} from "../../../../common/js/utils";
import MsReportErrorLog from './components/ErrorLog';
import MsReportLogDetails from './components/LogDetails';
import MsReportRequestStatistics from './components/RequestStatistics';
import MsReportTestOverview from './components/TestOverview';
import MsPerformancePressureConfig from "./components/PerformancePressureConfig";
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
export default {
name: "PerformanceReportView",
components: {
MsReportErrorLog,
MsReportLogDetails,
MsReportRequestStatistics,
MsReportTestOverview,
MsContainer,
MsMainContainer
},
data() {
return {
result: {},
active: '0',
reportId: '',
status: '',
reportName: '',
testId: '',
testName: '',
projectId: '',
projectName: '',
startTime: '0',
endTime: '0',
minutes: '0',
seconds: '0',
title: 'Logging',
report: {},
isReadOnly: false,
websocket: null,
dialogFormVisible: false,
import {checkoutTestManagerOrTestUser} from "@/common/js/utils";
export default {
name: "PerformanceReportView",
components: {
MsReportErrorLog,
MsReportLogDetails,
MsReportRequestStatistics,
MsReportTestOverview,
MsContainer,
MsMainContainer,
MsPerformancePressureConfig
},
data() {
return {
result: {},
active: '1',
reportId: '',
status: '',
reportName: '',
testId: '',
testName: '',
projectId: '',
projectName: '',
startTime: '0',
endTime: '0',
minutes: '0',
seconds: '0',
title: 'Logging',
report: {},
isReadOnly: false,
websocket: null,
dialogFormVisible: false,
testPlan: {testResourcePoolId: null}
}
},
methods: {
initBreadcrumb() {
if (this.reportId) {
this.result = this.$get("/performance/report/test/pro/info/" + this.reportId, res => {
let data = res.data;
if (data) {
this.reportName = data.name;
this.testId = data.testId;
this.testName = data.testName;
this.projectId = data.projectId;
this.projectName = data.projectName;
}
})
}
},
methods: {
initBreadcrumb() {
if (this.reportId) {
this.result = this.$get("/performance/report/test/pro/info/" + this.reportId, res => {
let data = res.data;
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.reportName = data.name;
this.testId = data.testId;
this.testName = data.testName;
this.projectId = data.projectId;
this.projectName = data.projectName;
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();
})
}
},
initReportTimeInfo() {
if (this.reportId) {
this.result = this.$get("/performance/report/content/report_time/" + this.reportId)
.then(res => {
let data = res.data.data;
if (data) {
this.startTime = data.startTime;
this.endTime = data.endTime;
let duration = data.duration;
this.minutes = Math.floor(duration / 60);
this.seconds = duration % 60;
}
}).catch(() => {
this.clearData();
})
}
},
initWebSocket() {
let protocol = "ws://";
if (window.location.protocol === 'https:') {
protocol = "wss://";
}
const uri = protocol + window.location.host + "/performance/report/" + this.reportId;
this.websocket = new WebSocket(uri);
this.websocket.onmessage = this.onMessage;
this.websocket.onopen = this.onOpen;
this.websocket.onerror = this.onError;
this.websocket.onclose = this.onClose;
},
checkReportStatus(status) {
switch (status) {
case 'Error':
this.$warning(this.$t('report.generation_error'));
break;
case 'Starting':
this.$alert(this.$t('report.start_status'));
break;
case 'Reporting':
case 'Running':
case 'Completed':
default:
break;
}
},
clearData() {
this.startTime = '0';
this.endTime = '0';
this.minutes = '0';
this.seconds = '0';
},
stopTest(forceStop) {
this.result = this.$get('/performance/stop/' + this.reportId + '/' + forceStop, () => {
this.$success(this.$t('report.test_stop_success'));
if (forceStop) {
this.$router.push('/performance/report/all');
} else {
this.report.status = 'Completed';
}
});
this.dialogFormVisible = false;
},
rerun(testId) {
this.$confirm(this.$t('report.test_rerun_confirm'), '', {
confirmButtonText: this.$t('commons.confirm'),
cancelButtonText: this.$t('commons.cancel'),
type: 'warning'
}).then(() => {
this.result = this.$post('/performance/run', {id: testId, triggerMode: 'MANUAL'}, (response) => {
this.reportId = response.data;
this.$router.push({path: '/performance/report/view/' + this.reportId});
// socket
this.initWebSocket();
})
}).catch(() => {
});
},
onOpen() {
window.console.log("socket opening.");
},
onError(e) {
window.console.error(e)
},
onMessage(e) {
this.$set(this.report, "refresh", e.data); //
this.$set(this.report, "status", 'Running');
this.initReportTimeInfo();
window.console.log('receive a message:', e.data);
},
onClose(e) {
this.$set(this.report, "refresh", Math.random()); //
this.$set(this.report, "status", 'Completed');
this.initReportTimeInfo();
window.console.log("socket closed.");
}
},
created() {
this.isReadOnly = false;
if (!checkoutTestManagerOrTestUser()) {
this.isReadOnly = true;
initWebSocket() {
let protocol = "ws://";
if (window.location.protocol === 'https:') {
protocol = "wss://";
}
this.reportId = this.$route.path.split('/')[4];
this.result = this.$get("/performance/report/" + this.reportId, res => {
let data = res.data;
if (data) {
this.status = data.status;
this.$set(this.report, "id", this.reportId);
this.$set(this.report, "status", data.status);
this.checkReportStatus(data.status);
if (this.status === "Completed" || this.status === "Running") {
this.initReportTimeInfo();
}
this.initBreadcrumb();
this.initWebSocket();
const uri = protocol + window.location.host + "/performance/report/" + this.reportId;
this.websocket = new WebSocket(uri);
this.websocket.onmessage = this.onMessage;
this.websocket.onopen = this.onOpen;
this.websocket.onerror = this.onError;
this.websocket.onclose = this.onClose;
},
checkReportStatus(status) {
switch (status) {
case 'Error':
this.$warning(this.$t('report.generation_error'));
break;
case 'Starting':
this.$alert(this.$t('report.start_status'));
break;
case 'Reporting':
case 'Running':
case 'Completed':
default:
break;
}
},
clearData() {
this.startTime = '0';
this.endTime = '0';
this.minutes = '0';
this.seconds = '0';
},
stopTest(forceStop) {
this.result = this.$get('/performance/stop/' + this.reportId + '/' + forceStop, () => {
this.$success(this.$t('report.test_stop_success'));
if (forceStop) {
this.$router.push('/performance/report/all');
} else {
this.$error(this.$t('report.not_exist'))
this.report.status = 'Completed';
}
});
this.dialogFormVisible = false;
},
beforeDestroy() {
this.websocket.close() //websocket
rerun(testId) {
this.$confirm(this.$t('report.test_rerun_confirm'), '', {
confirmButtonText: this.$t('commons.confirm'),
cancelButtonText: this.$t('commons.cancel'),
type: 'warning'
}).then(() => {
this.result = this.$post('/performance/run', {id: testId, triggerMode: 'MANUAL'}, (response) => {
this.reportId = response.data;
this.$router.push({path: '/performance/report/view/' + this.reportId});
// socket
this.initWebSocket();
})
}).catch(() => {
});
},
watch: {
'$route'(to) {
if (to.name === "perReportView") {
this.isReadOnly = false;
if (!checkoutTestManagerOrTestUser()) {
this.isReadOnly = true;
}
let reportId = to.path.split('/')[4];
this.reportId = reportId;
if (reportId) {
this.$get("/performance/report/test/pro/info/" + reportId, response => {
let data = response.data;
if (data) {
this.status = data.status;
this.reportName = data.name;
this.testName = data.testName;
this.testId = data.testId;
this.projectName = data.projectName;
onOpen() {
window.console.log("socket opening.");
},
onError(e) {
window.console.error(e)
},
onMessage(e) {
this.$set(this.report, "refresh", e.data); //
this.$set(this.report, "status", 'Running');
this.initReportTimeInfo();
window.console.log('receive a message:', e.data);
},
onClose(e) {
this.$set(this.report, "refresh", Math.random()); //
this.$set(this.report, "status", 'Completed');
this.initReportTimeInfo();
window.console.log("socket closed.");
}
},
created() {
this.isReadOnly = false;
if (!checkoutTestManagerOrTestUser()) {
this.isReadOnly = true;
}
this.reportId = this.$route.path.split('/')[4];
this.result = this.$get("/performance/report/" + this.reportId, res => {
let data = res.data;
if (data) {
this.status = data.status;
this.$set(this.report, "id", this.reportId);
this.$set(this.report, "status", data.status);
this.$set(this.report, "testId", data.testId);
this.$set(this.report, "loadConfiguration", data.loadConfiguration);
this.checkReportStatus(data.status);
if (this.status === "Completed" || this.status === "Running") {
this.initReportTimeInfo();
}
this.initBreadcrumb();
this.initWebSocket();
} else {
this.$error(this.$t('report.not_exist'))
}
});
this.$set(this.report, "id", reportId);
this.$set(this.report, "status", data.status);
},
beforeDestroy() {
this.websocket.close() //websocket
},
watch: {
'$route'(to) {
if (to.name === "perReportView") {
this.isReadOnly = false;
if (!checkoutTestManagerOrTestUser()) {
this.isReadOnly = true;
}
let reportId = to.path.split('/')[4];
this.reportId = reportId;
if (reportId) {
this.$get("/performance/report/test/pro/info/" + reportId, response => {
let data = response.data;
if (data) {
this.status = data.status;
this.reportName = data.name;
this.testName = data.testName;
this.testId = data.testId;
this.projectName = data.projectName;
this.checkReportStatus(data.status);
if (this.status === "Completed") {
this.result = this.$get("/performance/report/content/report_time/" + this.reportId).then(res => {
let data = res.data.data;
if (data) {
this.startTime = data.startTime;
this.endTime = data.endTime;
let duration = data.duration;
this.minutes = Math.floor(duration / 60);
this.seconds = duration % 60;
}
}).catch(() => {
this.clearData();
})
} else {
this.$set(this.report, "id", reportId);
this.$set(this.report, "status", data.status);
this.checkReportStatus(data.status);
if (this.status === "Completed") {
this.result = this.$get("/performance/report/content/report_time/" + this.reportId).then(res => {
let data = res.data.data;
if (data) {
this.startTime = data.startTime;
this.endTime = data.endTime;
let duration = data.duration;
this.minutes = Math.floor(duration / 60);
this.seconds = duration % 60;
}
}).catch(() => {
this.clearData();
}
})
} else {
this.$error(this.$t('report.not_exist'));
this.clearData();
}
});
} else {
this.$error(this.$t('report.not_exist'));
}
});
}
}
}
}
}
}
</script>
<style scoped>
.ms-report-view-btns {
margin-top: 15px;
}
.ms-report-view-btns {
margin-top: 15px;
}
.ms-report-time-desc {
text-align: left;
display: block;
color: #5C7878;
}
.ms-report-time-desc {
text-align: left;
display: block;
color: #5C7878;
}
</style>

View File

@ -0,0 +1,311 @@
<template>
<div v-loading="result.loading" class="pressure-config-container">
<el-row>
<el-col :span="10">
<el-form :inline="true">
<el-form-item>
<div class="config-form-label">{{ $t('load_test.thread_num') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
:disabled="true"
:placeholder="$t('load_test.input_thread_num')"
v-model="threadNumber"
@change="calculateChart"
:min="1"
size="mini"/>
</el-form-item>
</el-form>
<el-form :inline="true">
<el-form-item>
<div class="config-form-label">{{ $t('load_test.duration') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
:disabled="true"
:placeholder="$t('load_test.duration')"
v-model="duration"
:min="1"
@change="calculateChart"
size="mini"/>
</el-form-item>
</el-form>
<el-form :inline="true">
<el-form-item>
<el-form-item>
<div class="config-form-label">{{ $t('load_test.rps_limit') }}</div>
</el-form-item>
<el-form-item>
<el-switch v-model="rpsLimitEnable"/>
</el-form-item>
<el-form-item>
<el-input-number
:disabled="true"
:placeholder="$t('load_test.input_rps_limit')"
v-model="rpsLimit"
@change="calculateChart"
:min="1"
size="mini"/>
</el-form-item>
</el-form-item>
</el-form>
<el-form :inline="true" class="input-bottom-border">
<el-form-item>
<div>{{ $t('load_test.ramp_up_time_within') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
:disabled="true"
placeholder=""
:min="1"
:max="duration"
v-model="rampUpTime"
@change="calculateChart"
size="mini"/>
</el-form-item>
<el-form-item>
<div>{{ $t('load_test.ramp_up_time_minutes') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
:disabled="true"
placeholder=""
:min="1"
:max="Math.min(threadNumber, rampUpTime)"
v-model="step"
@change="calculateChart"
size="mini"/>
</el-form-item>
<el-form-item>
<div>{{ $t('load_test.ramp_up_time_times') }}</div>
</el-form-item>
</el-form>
</el-col>
<el-col :span="14">
<div class="title">{{ $t('load_test.pressure_prediction_chart') }}</div>
<chart class="chart-container" ref="chart1" :options="orgOptions" :autoresize="true"></chart>
</el-col>
</el-row>
</div>
</template>
<script>
import echarts from "echarts";
const TARGET_LEVEL = "TargetLevel";
const RAMP_UP = "RampUp";
const STEPS = "Steps";
const DURATION = "duration";
const RPS_LIMIT = "rpsLimit";
const RPS_LIMIT_ENABLE = "rpsLimitEnable";
export default {
name: "MsPerformancePressureConfig",
props: ['report'],
data() {
return {
result: {},
threadNumber: 10,
duration: 10,
rampUpTime: 10,
step: 10,
rpsLimit: 10,
rpsLimitEnable: false,
orgOptions: {},
}
},
mounted() {
this.getLoadConfig();
},
methods: {
calculateLoadConfiguration: function (data) {
data.forEach(d => {
switch (d.key) {
case TARGET_LEVEL:
this.threadNumber = d.value;
break;
case RAMP_UP:
this.rampUpTime = d.value;
break;
case DURATION:
this.duration = d.value;
break;
case STEPS:
this.step = d.value;
break;
case RPS_LIMIT:
this.rpsLimit = d.value;
break;
default:
break;
}
});
this.threadNumber = this.threadNumber || 10;
this.duration = this.duration || 30;
this.rampUpTime = this.rampUpTime || 12;
this.step = this.step || 3;
this.rpsLimit = this.rpsLimit || 10;
this.calculateChart();
},
getLoadConfig() {
if (!this.report.id) {
return;
}
this.$get("/performance/report/" + this.report.id, res => {
let data = res.data;
if (data) {
if (data.loadConfiguration) {
let d = JSON.parse(data.loadConfiguration);
this.calculateLoadConfiguration(d);
} else {
this.$get('/performance/get-load-config/' + this.report.testId, (response) => {
if (response.data) {
let data = JSON.parse(response.data);
this.calculateLoadConfiguration(data);
}
});
}
} else {
this.$error(this.$t('report.not_exist'))
}
});
},
calculateChart() {
if (this.duration < this.rampUpTime) {
this.rampUpTime = this.duration;
}
if (this.rampUpTime < this.step) {
this.step = this.rampUpTime;
}
this.orgOptions = {
xAxis: {
type: 'category',
boundaryGap: false,
data: []
},
yAxis: {
type: 'value'
},
tooltip: {
trigger: 'axis',
formatter: '{a}: {c0}',
axisPointer: {
lineStyle: {
color: '#57617B'
}
}
},
series: [{
name: 'User',
data: [],
type: 'line',
step: 'start',
smooth: false,
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(137, 189, 27, 0.3)'
}, {
offset: 0.8,
color: 'rgba(137, 189, 27, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(137,189,27)',
borderColor: 'rgba(137,189,2,0.27)',
borderWidth: 12
}
},
}]
};
let timePeriod = Math.floor(this.rampUpTime / this.step);
let timeInc = timePeriod;
let threadPeriod = Math.floor(this.threadNumber / this.step);
let threadInc1 = Math.floor(this.threadNumber / this.step);
let threadInc2 = Math.ceil(this.threadNumber / this.step);
let inc2count = this.threadNumber - this.step * threadInc1;
for (let i = 0; i <= this.duration; i++) {
// x
this.orgOptions.xAxis.data.push(i);
if (i > timePeriod) {
timePeriod += timeInc;
if (inc2count > 0) {
threadPeriod = threadPeriod + threadInc2;
inc2count--;
} else {
threadPeriod = threadPeriod + threadInc1;
}
if (threadPeriod > this.threadNumber) {
threadPeriod = this.threadNumber;
}
this.orgOptions.series[0].data.push(threadPeriod);
} else {
this.orgOptions.series[0].data.push(threadPeriod);
}
}
},
},
watch: {
report: {
handler(val) {
if (!val.testId) {
return;
}
this.getLoadConfig();
},
deep: true
}
}
}
</script>
<style scoped>
.pressure-config-container .el-input {
width: 130px;
}
.pressure-config-container .config-form-label {
width: 130px;
}
.pressure-config-container .input-bottom-border input {
border: 0;
border-bottom: 1px solid #DCDFE6;
}
.chart-container {
width: 100%;
}
.el-col .el-form {
margin-top: 15px;
text-align: left;
}
.el-col {
margin-top: 15px;
text-align: left;
}
.title {
margin-left: 60px;
}
</style>

View File

@ -21,6 +21,8 @@
</el-menu-item>
<el-menu-item index="/setting/organizationworkspace" v-permission="['org_admin']">{{$t('commons.workspace')}}
</el-menu-item>
<el-menu-item index="/setting/serviceintegration" v-permission="['org_admin']">{{$t('organization.service_integration')}}
</el-menu-item>
</el-submenu>
<el-submenu index="3" v-permission="['test_manager']" v-if="isCurrentWorkspaceUser">

View File

@ -0,0 +1,85 @@
<template>
<el-card class="header-title">
<div>
<div>{{$t('organization.select_defect_platform')}}</div>
<el-radio-group v-model="platform" style="margin-top: 10px">
<el-radio v-for="(item, index) in platforms" :key="index" :label="item.value" size="small">
{{item.name}}
</el-radio>
</el-radio-group>
</div>
<div style="width: 500px">
<div style="margin-top: 20px;margin-bottom: 10px">{{$t('organization.basic_auth_info')}}</div>
<el-form :model="form" ref="form" label-width="100px" size="small">
<el-form-item :label="$t('organization.api_account')" prop="account">
<el-input v-model="form.account" :placeholder="$t('organization.input_api_account')"/>
</el-form-item>
<el-form-item :label="$t('organization.api_password')" prop="password">
<el-input v-model="form.password" auto-complete="new-password" :placeholder="$t('organization.input_api_password')" show-password/>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="submit('form')" style="width: 400px">
{{$t('commons.save')}}
</el-button>
</el-form-item>
</el-form>
</div>
<div class="defect-tip">
<div>{{$t('organization.use_tip')}}</div>
<div>
1. {{$t('organization.use_tip_one')}}
</div>
<div>
2. {{$t('organization.use_tip_two')}}
<router-link to="/track/project/all" style="margin-left: 5px">{{$t('organization.link_the_project_now')}}</router-link>
</div>
</div>
</el-card>
</template>
<script>
export default {
name: "DefectManagement",
data() {
return {
form: {},
platform: '',
platforms: [
{
name: 'TAPD',
value: 'tapd',
},
{
name: 'JIRA',
value: 'jira',
}
],
rules: {
account: {required: true, message: this.$t('organization.input_api_account'), trigger: ['change', 'blur']},
password: {required: true, message: this.$t('organization.input_api_password'), trigger: ['change', 'blur']}
},
}
},
methods: {
submit(form) {
}
}
}
</script>
<style scoped>
.header-title {
padding: 10px 30px;
}
.defect-tip {
background: #EDEDED;
border: solid #E1E1E1 1px;
margin: 10px 0;
padding: 10px;
border-radius: 3px;
}
</style>

View File

@ -0,0 +1,30 @@
<template>
<div>
<el-tabs class="system-setting" v-model="activeName">
<el-tab-pane :label="$t('organization.defect_manage')" name="defect">
<defect-management/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import DefectManagement from "./DefectManagement";
export default {
name: "ServiceIntegration",
components: {
DefectManagement
},
data() {
return {
activeName: 'defect'
}
}
}
</script>
<style scoped>
</style>

View File

@ -55,6 +55,7 @@
</el-table-column>
<el-table-column
prop="num"
sortable="custom"
:label="$t('commons.id')"
show-overflow-tooltip>
</el-table-column>
@ -497,6 +498,10 @@
this.initTableData();
},
sort(column) {
//
if (this.condition.orders) {
this.condition.orders = [];
}
_sort(column, this.condition);
this.initTableData();
},

View File

@ -111,3 +111,41 @@ export const MOCKJS_FUNC = [
{name: '@id'},
{name: '@increment'}
]
export const JMETER_FUNC = [
{name: "${__threadNum}"},
{name: "${__samplerName}"},
{name: "${__machineIP}"},
{name: "${__machineName}"},
{name: "${__time}"},
{name: "${__log}"},
{name: "${__logn}"},
{name: "${__StringFromFile}"},
{name: "${__FileToString}"},
{name: "${__CSVRead}"},
{name: "${__XPath}"},
{name: "${__counter}"},
{name: "${__intSum}"},
{name: "${__longSum}"},
{name: "${__Random}"},
{name: "${__RandomString}"},
{name: "${__UUID}"},
{name: "${__BeanShell}"},
{name: "${__javaScript}"},
{name: "${__jexl}"},
{name: "${__jexl2}"},
{name: "${__property}"},
{name: "${__P}"},
{name: "${__setProperty}"},
{name: "${__split}"},
{name: "${__V}"},
{name: "${__eval}"},
{name: "${__evalVar}"},
{name: "${__regexFunction}"},
{name: "${__escapeOroRegexpChars}"},
{name: "${__char}"},
{name: "${__unescape}"},
{name: "${__unescapeHtml}"},
{name: "${__escapeHtml}"},
{name: "${__TestPlanName}"},
]

View File

@ -173,8 +173,18 @@ export default {
special_characters_are_not_supported: 'Incorrect format (special characters are not supported and cannot end with \'-\')',
none: 'None Organization',
select: 'Select Organization',
service_integration: 'Service integration',
defect_manage: 'Defect management platform',
select_defect_platform: 'Please select the defect management platform to be integrated:',
basic_auth_info: 'Basic Auth account information:',
api_account: 'API account',
api_password: 'API password',
input_api_account: 'please enter account',
input_api_password: 'Please enter password',
use_tip: 'Usage guidelines:',
use_tip_one: 'Basic Auth account information is queried in "Company Management-Security and Integration-Open Platform"',
use_tip_two: 'After saving the Basic Auth account information, you need to manually associate the ID/key in the Metersphere project',
link_the_project_now: 'Link the project now',
},
project: {
name: 'Project name',
@ -387,6 +397,7 @@ export default {
path_description: "etc/login",
parameters: "Query parameters",
parameters_filter_example: "Example",
parameters_filter_tips: "Only support MockJs function result preview",
parameters_advance: "Advanced parameter settings",
parameters_preview: "Preview",
parameters_preview_warning: "Please enter the template first",
@ -497,9 +508,9 @@ export default {
execution_result: ": Please select the execution result",
actual_result: ": The actual result is empty",
case: {
input_test_case:'Please enter the associated case name',
test_name:'TestName',
other:'--Other--',
input_test_case: 'Please enter the associated case name',
test_name: 'TestName',
other: '--Other--',
test_case: "Case",
move: "Move case",
case_list: "Test case list",

View File

@ -174,6 +174,18 @@ export default {
none: '无组织',
select: '选择组织',
delete_warning: '删除该组织将同步删除该组织下所有相关工作空间和相关工作空间下的所有项目,以及项目中的所有用例、接口测试、性能测试等,确定要删除吗?',
service_integration: '服务集成',
defect_manage: '缺陷管理平台',
select_defect_platform: '请选择要集成的缺陷管理平台:',
basic_auth_info: 'Basic Auth 账号信息:',
api_account: 'API 账号',
api_password: 'API 口令',
input_api_account: '请输入账号',
input_api_password: '请输入口令',
use_tip: '使用指引:',
use_tip_one: 'Basic Auth 账号信息在"公司管理-安全与集成-开放平台"中查询',
use_tip_two: '保存 Basic Auth 账号信息后,需要在 Metersphere 项目中手动关联 ID/key',
link_the_project_now: '马上关联项目',
},
project: {
recent: '最近的项目',
@ -387,6 +399,7 @@ export default {
parameters_filter: "内置函数",
parameters_filter_desc: "使用方法",
parameters_filter_example: "示例",
parameters_filter_tips: "只支持 MockJs 函数结果预览",
parameters_advance: "高级参数设置",
parameters_preview: "预览",
parameters_preview_warning: "请先输入模版",

View File

@ -172,7 +172,18 @@ export default {
none: '無組織',
select: '選擇組織',
delete_warning: '删除该组织将同步删除该组织下所有相关工作空间和相关工作空间下的所有项目,以及项目中的所有用例、接口测试、性能测试等,确定要删除吗?',
service_integration: '服務集成',
defect_manage: '缺陷管理平台',
select_defect_platform: '請選擇要集成的缺陷管理平台:',
basic_auth_info: 'Basic Auth 賬號信息:',
api_account: 'API 賬號',
api_password: 'API 口令',
input_api_account: '請輸入賬號',
input_api_password: '請輸入口令',
use_tip: '使用指引:',
use_tip_one: 'Basic Auth 賬號信息在"公司管理-安全與集成-開放平台"中查詢',
use_tip_two: '保存 Basic Auth 賬號信息後,需要在 Metersphere 項目中手動關聯 ID/key',
link_the_project_now: '馬上關聯項目',
},
project: {
recent: '最近的項目',
@ -386,6 +397,7 @@ export default {
url_invalid: "URL無效",
parameters: "請求參數",
parameters_filter_example: "示例",
parameters_filter_tips: "只支持MockJs函數結果預覽",
parameters_advance: "高級參數設置",
parameters_preview: "預覽",
parameters_preview_warning: "請先輸入模版",
@ -496,9 +508,9 @@ export default {
execution_result: ": 請選擇執行結果",
actual_result: ": 實際結果為空",
case: {
input_test_case:'請輸入關聯用例名稱',
test_name:'測試名稱',
other:'--其他--',
input_test_case: '請輸入關聯用例名稱',
test_name: '測試名稱',
other: '--其他--',
test_case: "測試用例",
move: "移動用例",
case_list: "用例列表",